ruby inheritance - ruby

How to implement inheritance in ruby for the following?
class Land
attr_accessor :name, :area
def initialize(name, area)
#name = name
#area = area
end
end
class Forest < Land
attr_accessor :rain_level
attr_reader :name
def name=(_name)
begin
raise "could not set name"
rescue Exception => e
puts e.message
end
end
def initialize(land, rain_level)
#name = land.name
#rain_level = rain_level
end
end
l = Land.new("land", 2300)
f = Forest.new(l, 400)
puts f.name # => "land"
suppose when i change name for land l, then it should change for sub class also
l.name ="new land"
puts f.name # => "land"
what expected is puts f.name # => "new land"

It seems to me that this is not actually inheritance in the OO sense. If you change Forest so that it holds a reference to the Land then you will get the behavior you wanted.
class Forest
attr_accessor :rain_level
def name
#land.name
end
def initialize(land, rain_level)
#land = land
#rain_level = rain_level
end
end

This is kind of an interesting thing you want to build.
Summarizing you want to have two objects that share a value but only one is allowed to edit the value, the other one is only allowed to read it.
I think the easiest way to implement this is in your case to implement a new getter in Forest which returns land.name. By writing l.name = 'meow' will f.name return moew too because it holds a reference to l.
Hope this helps.

Related

How do I pull a name from a class in an Array?

Okay, this is a little hard to explain but I will try (For starters I am only just learning to code so it may be something super simple I'm missing..)
I created a few classes, I put a name in those classes, I put them in an array, I then chose one at random and try to puts the name, and it outputs blank.
Am I doing this all completely wrong? I've been learning ruby for about 3 months now so I'm sure there is a lot I don't know.
class A
attr :name
def set_name
#name = "Aaa"
end
def get_name
return #name
end
end
class B
attr :name
def set_name
#name = "Bbb"
end
def get_name
return #name
end
end
class C
attr :name
def set_name
#name = "Ccc"
end
def get_name
return #name
end
end
name_a = A.new
name_b = B.new
name_c = C.new
which_name = Array.new
which_name[0] = name_a
which_name[1] = name_b
which_name[2] = name_c
roll = rand(max 3)
puts which_name[roll].get_name
I then chose one at random and try to puts the name, and it outputs
blank.
You never called the #set_name method in your code. You can add this:
name_a.set_name
name_b.set_name
name_c.set_name
Also, you probably want to look into #attr_accessor.

Possible help in code refactoring

Sandi Metz says in SOLID OOPS concepts from GORUCO that presence of if..else blocks in Ruby can be considered to be a deviation from Open-Close Principle. What all methods can be used to avoid not-urgent if..else conditions? I tried the following code:
class Fun
def park(s=String.new)
puts s
end
def park(i=Fixnum.new)
i=i+2
end
end
and found out that function overloading does not work in Ruby. What are other methods through which the code can be made to obey OCP?
I could have simply gone for:
class Fun
def park(i)
i=i+2 if i.class==1.class
puts i if i.class=="asd".class
end
end
but this is in violation to OCP.
With your current example, and wanting to avoid type detection, I would use Ruby's capability to re-open classes to add functionality you need to Integer and String:
class Integer
def park
puts self + 2
end
end
class String
def park
puts self
end
end
This would work more cleanly when altering your own classes. But maybe it doesn't fit your conceptual model (it depends what Fun represents, and why it can take those two different classes in a single method).
An equivalent but keeping your Fun class might be:
class Fun
def park_fixnum i
puts i + 2
end
def park_string s
puts s
end
def park param
send("park_#{param.class.to_s.downcase}", param)
end
end
As an opinion, I am not sure you will gain much writing Ruby in this way. The principles you are learning may be good ones (I don't know), but applying them forcefully "against the grain" of the language may create less readable code, regardless of whether it meets a well-intentioned design.
So what I would probably do in practice is this:
class Fun
def park param
case param
when Integer
puts param + 2
when String
puts param
end
end
end
This does not meet your principles, but is idiomatic Ruby and slightly easier to read and maintain than an if block (where the conditions could be far more complex so take longer for a human to parse).
You could just create handled classes for Fun like so
class Fun
def park(obj)
#parker ||= Object.const_get("#{obj.class}Park").new(obj)
#parker.park
rescue NameError => e
raise ArgumentError, "expected String or Fixnum but recieved #{obj.class.name}"
end
end
class Park
def initialize(p)
#park = p
end
def park
#park
end
end
class FixnumPark < Park
def park
#park += 2
end
end
class StringPark < Park
end
Then things like this will work
f = Fun.new
f.park("string")
#=> "string"
f.instance_variable_get("#parker")
#=> #<StringPark:0x1e04b48 #park="string">
f = Fun.new
f.park(2)
#=> 4
f.instance_variable_get("#parker")
#=> #<FixnumPark:0x1e04b48 #park=4>
f.park(22)
#=> 6 because the instance is already loaded and 4 + 2 = 6
Fun.new.park(12.3)
#=> ArgumentError: expected String or Fixnum but received Float
You could do something like this:
class Parent
attr_reader :s
def initialize(s='')
#s = s
end
def park
puts s
end
end
class Child1 < Parent
attr_reader :x
def initialize(s, x)
super(s)
#x = x
end
def park
puts x
end
end
class Child2 < Parent
attr_reader :y
def initialize(s, y)
super(s)
#y = y
end
def park
puts y
end
end
objects = [
Parent.new('hello'),
Child1.new('goodbye', 1),
Child2.new('adios', 2),
]
objects.each do |obj|
obj.park
end
--output:--
hello
1
2
Or, maybe I overlooked one of your twists:
class Parent
attr_reader :x
def initialize(s='')
#x = s
end
def park
puts x
end
end
class Child1 < Parent
def initialize(x)
super
end
def park
x + 2
end
end
class Child2 < Parent
def initialize(x)
super
end
def park
x * 2
end
end
objects = [
Parent.new('hello'),
Child1.new(2),
Child2.new(100),
]
results = objects.map do |obj|
obj.park
end
p results
--output:--
hello
[nil, 4, 200]
And another example using blocks, which are like anonymous functions. You can pass in the desired behavior to park() as a function:
class Function
attr_reader :block
def initialize(&park)
#block = park
end
def park
raise "Not implemented"
end
end
class StringFunction < Function
def initialize(&park)
super
end
def park
block.call
end
end
class AdditionFunction < Function
def initialize(&park)
super
end
def park
block.call 1
end
end
class DogFunction < Function
class Dog
def bark
puts 'woof, woof'
end
end
def initialize(&park)
super
end
def park
block.call Dog.new
end
end
objects = [
StringFunction.new {puts 'hello'},
AdditionFunction.new {|i| i+2},
DogFunction.new {|dog| dog.bark},
]
results = objects.map do |obj|
obj.park
end
p results
--output:--
hello
woof, woof
[nil, 3, nil]
Look at the is_a? method
def park(i)
i.is_a?(Fixnum) ? (i + 2) : i
end
But even better not to check a type, but use duck typing:
def park(i)
i.respond_to?(:+) ? (i + 2) : i
end
UPD: After reading comments. Yes, both examples above don't solve the OCP problem. That is how I would do it:
class Fun
# The method doesn't know how to pluck data. But it knows a guy
# who knows the trick
def pluck(i)
return __pluck_string__(i) if i.is_a? String
__pluck_fixnum__(i) if i.is_a? Fixnum
end
private
# Every method is responsible for plucking data in some special way
# Only one cause of possible changes for each of them
def __pluck_string__(i)
puts i
end
def __pluck_fixnum__(i)
i + 2
end
end
I understand or equal to operation in ruby but can you explain what
you have done with:
Object.const_get("#{obj.class}Park").new(obj)
In ruby, something that starts with a capital letter is a constant. Here is a simpler example of how const_get() works:
class Dog
def bark
puts 'woof'
end
end
dog_class = Object.const_get("Dog")
dog_class.new.bark
--output:--
woof
Of course, you can also pass arguments to dog_class.new:
class Dog
attr_reader :name
def initialize(name)
#name = name
end
def bark
puts "#{name} says woof!"
end
end
dog_class = Object.const_get("Dog")
dog_class.new('Ralph').bark
--output:--
Ralph says woof!
And the following line is just a variation of the above:
Object.const_get("#{obj.class}Park").new(obj)
If obj = 'hello', the first portion:
Object.const_get("#{obj.class}Park")
is equivalent to:
Object.const_get("#{String}Park")
And when the String class object is interpolated into a string, it is simply converted to the string "String", giving you:
Object.const_get("StringPark")
And that line retrieves the StringPark class, giving you:
Object.const_get("StringPark")
|
V
StringPark
Then, adding the second portion of the original line gives you:
StringPark.new(obj)
And because obj = 'hello', that is equivalent to:
StringPark.new('hello')
Capice?

Ruby Hash whose key is a function of the object?

For instance,
s1 = Student.new(1, "Bob", "Podunk High")
hash[1] = s1
puts hash[1].name #produces "Bob"
s1.id = 15
puts hash[15].name #produces "Bob"
puts hash[1].name #fails
This is not exactly Hash-like behavior and insertions with the wrong key still needs to be defined.
While I can certainly roll my own container that behaves this way but it will be hard to make it fast, ie not search through the whole container every time [] is called. Just wondering if someone smarter has already made something I can steal.
EDIT: Some good ideas below helped me focus my requirements:
avoid the O(n) lookup time
allow multiple containers to the same object (association not composition)
have different data types (eg. that might use name instead of id) without too much reimplementation
You can implement it yourself.
Look at the draft solution:
class Campus
attr_reader :students
def initialize
#students = []
end
def [](ind)
students.detect{|s| s.id == ind}
end
def <<(st)
raise "Yarrr, not a student" if st.class != Student
raise "We already have got one with id #{st.id}" if self[st.id]
students << st
end
end
class Student
attr_accessor :id, :name, :prop
def initialize(id, name, prop)
#id, #name, #prop = id, name, prop
end
end
campus = Campus.new
st1 = Student.new(1, "Pedro", "Math")
st2 = Student.new(2, "Maria", "Opera")
campus << st1
campus << st2
campus[1]
#=> Student...id:1,name:pedro...
campus[2].name
#=> Maria
campus[2].id = 10
campus[2]
#=> error
campus[10].name
#=> Maria
Or you can play around Array class (or Hash, if you really need it):
class StrangeArray < Array
def [](ind)
self.detect{|v| v.id == ind} || raise "nothing found" # if you really need to raise an error
end
def <<(st)
raise "Looks like a duplicate" if self[st.id]
self.push(st)
end
end
campus = StrangeArray.new
campus << Student.new(15, 'Michael', 'Music')
campus << Student.new(40, 'Lisa', 'Medicine')
campus[1]
#=> error 'not found'
campus[15].prop
#=> Music
campus[15].id = 20
campus[20].prop
#=> Music
etc
And after #tadman's correct comment you can use reference to your hash right into your Student class:
class Student
attr_accessor :name, :prop
attr_reader :id, :campus
def initialize(id, name, prop, camp=nil)
#id, #name, #prop = id, name, prop
self.campus = camp if camp
end
def id=(new_id)
if campus
rase "this id is already taken in campus" if campus[new_id]
campus.delete id
campus[new_id] = self
end
#id = new_id
end
def campus=(camp)
rase "this id is already taken in campus" if camp[id]
#campus = camp
camp[#id] = self
end
end
campus = {}
st1 = Student.new(1, "John", "Math")
st2 = Student.new(2, "Lisa", "Math", campus)
# so now in campus is only Lisa
st1.campus = campus
# we've just pushed John in campus
campus[1].name
#=> John
campus[1].id = 10
campus[10].name
#=> John
While the Hash object might not behave the way you want it to, you can always customize the objects being inserted to be hashed a particular way.
You can do this by adding two new methods to your existing class:
class Student
def hash
self.id
end
def eql?(student)
self.id == student.id
end
end
By defining hash to return a value based on id, Hash will consider these two candidates for the same spot in the hash. The second definition declares "hash equivalence" between any two objects that have the same hash value.
This will work well provided your id values fit into a conventional 32-bit Fixnum and aren't 64-bit BIGINT database values.
As fl00r points out, this will only work if your id is immutable. For most databases this tends to be the case. Changing id on the fly is probably a really bad idea, though, as it can lead to total chaos and mind-blowing bugs.
This is a hard problem. Database vendors can make money because it is a hard problem. You are basically looking to implement traditional RDBMS indices: search through derived data, to provide fast lookup to the data it was derived from, while allowing that data to change. If you want to access the data from multiple threads, you'll quickly run into all the issues that make it hard to make a database ACID compliant.
I suggest putting the data into a database, adding the necessary indices and letting the database -- an application optimized for this exact purpose -- do the work.
The container must be notified when your key has been changed, otherwise you must search the key on the fly in lg(n).
If you rarely change key and lookup a lot, just rebuild the hash:
def build_hash_on_attribute(objects, attribute)
Hash[objects.collect { |e| [e.send(method), e] }]
end
s1 = OpenStruct.new id: 1, name: 's1'
h = build_hash_on_attribute([s1], :id)
h[1].name # => 's1'
h[1].id = 15
# rebuild the whole index after any key attribute has been changed
h = build_hash_on_attribute(h.values, :id)
h[1] # => nil
h[15].name # => 's1'
Update 02/12: Add a solution using observer pattern
Or you do need such automatically index building, you can use observer pattern like below or decorator pattern. But you need to use the wrapped objects in decorator pattern.
gist: https://gist.github.com/1807324
module AttrChangeEmitter
def self.included(base)
base.extend ClassMethods
base.send :include, InstanceMethods
end
module ClassMethods
def attr_change_emitter(*attrs)
attrs.each do |attr|
class_eval do
alias_method "#{attr}_without_emitter=", "#{attr}="
define_method "#{attr}_with_emitter=" do |v|
previous_value = send("#{attr}")
send "#{attr}_without_emitter=", v
attr_change_listeners_on(attr).each do |listener|
listener.call self, previous_value, v
end
end
alias_method "#{attr}=", "#{attr}_with_emitter="
end
end
end
end
module InstanceMethods
def attr_change_listeners_on(attr)
#attr_change_listeners_on ||= {}
#attr_change_listeners_on[attr.to_sym] ||= []
end
def add_attr_change_listener_on(attr, block)
listeners = attr_change_listeners_on(attr)
listeners << block unless listeners.include?(block)
end
def remove_attr_change_listener_on(attr, block)
attr_change_listeners_on(attr).delete block
end
end
end
class AttrChangeAwareHash
include Enumerable
def initialize(attr = :id)
#attr = attr.to_sym
#hash = {}
end
def each(&block)
#hash.values.each(&block)
end
def on_entity_attr_change(e, previous_value, new_value)
if #hash[previous_value].equal? e
#hash.delete(previous_value)
# remove the original one in slot new_value
delete_by_key(new_value)
#hash[new_value] = e
end
end
def add(v)
delete(v)
v.add_attr_change_listener_on(#attr, self.method(:on_entity_attr_change))
k = v.send(#attr)
#hash[k] = v
end
alias_method :<<, :add
def delete(v)
k = v.send(#attr)
delete_by_key(k) if #hash[k].equal?(v)
end
def delete_by_key(k)
v = #hash.delete(k)
v.remove_attr_change_listener_on(#attr, self.method(:on_entity_attr_change)) if v
v
end
def [](k)
#hash[k]
end
end
class Student
include AttrChangeEmitter
attr_accessor :id, :name
attr_change_emitter :id, :name
def initialize(id, name)
self.id = id
self.name = name
end
end
indexByIDA = AttrChangeAwareHash.new(:id)
indexByIDB = AttrChangeAwareHash.new(:id)
indexByName = AttrChangeAwareHash.new(:name)
s1 = Student.new(1, 'John')
s2 = Student.new(2, 'Bill')
s3 = Student.new(3, 'Kate')
indexByIDA << s1
indexByIDA << s3
indexByIDB << s1
indexByIDB << s2
indexByName << s1
indexByName << s2
indexByName << s3
puts indexByIDA[1].name # => John
puts indexByIDB[2].name # => Bill
puts indexByName['John'].id # => 1
s2.id = 15
s2.name = 'Batman'
p indexByIDB[2] # => nil
puts indexByIDB[15].name # => Batman
indexByName.each do |v|
v.name = v.name.downcase
end
p indexByName['John'] # => nil
puts indexByName['john'].id # => 1
p indexByName.collect { |v| [v.id, v.name] }
# => [[1, "john"], [3, "kate"], [15, "batman"]]
indexByName.delete_by_key 'john'
indexByName.delete(s2)
s2.id = 1 # set batman id to 1 to overwrite john
p indexByIDB.collect { |v| [v.id, v.name] }
# => [[1, "batman"]]
p indexByName.collect { |v| [v.id, v.name] }
# => [[3, "kate"]]

How do I write a writer method for a class variable in Ruby?

I'm studying Ruby and my brain just froze.
In the following code, how would I write the class writer method for 'self.total_people'? I'm trying to 'count' the number of instances of the class 'Person'.
class Person
attr_accessor :name, :age
##nationalities = ['French', 'American', 'Colombian', 'Japanese', 'Russian', 'Peruvian']
##current_people = []
##total_people = 0
def self.nationalities #reader
##nationalities
end
def self.nationalities=(array=[]) #writer
##nationalities = array
end
def self.current_people #reader
##current_people
end
def self.total_people #reader
##total_people
end
def self.total_people #writer
#-----?????
end
def self.create_with_attributes(name, age)
person = self.new(name)
person.age = age
person.name = name
return person
end
def initialize(name="Bob", age=0)
#name = name
#age = age
puts "A new person has been instantiated."
##total_people =+ 1
##current_people << self
end
You can define one by appending the equals sign to the end of the method name:
def self.total_people=(v)
##total_people = v
end
You're putting all instances in ##current_people you could define total_people more accurately:
def self.total_people
##current_people.length
end
And get rid of all the ##total_people related code.
I think this solves your problem:
class Person
class << self
attr_accessor :foobar
end
self.foobar = 'hello'
end
p Person.foobar # hello
Person.foobar = 1
p Person.foobar # 1
Be aware of the gotchas with Ruby's class variables with inheritance - Child classes cannot override the parent's value of the class var. A class instance variable may really be what you want here, and this solution goes in that direction.
One approach that didn't work was the following:
module PersonClassAttributes
attr_writer :nationalities
end
class Person
extend PersonClassAttributes
end
I suspect it's because attr_writer doesn't work with modules for some reason.
I'd like to know if there's some metaprogramming way to approach this. However, have you considered creating an object that contains a list of people?

ruby alias_method question

I have following code
class User
attr_accessor :name
end
u = User.new
u.name = 'john'
puts u.name #=> john
In the above case everything works. However this code does not work
class User
attr_accessor :name
end
u = User.new
u.name = 'john'
u.name('john')
In order to fix that issue, I have decided to use alias_method. I know there are other ways to solve the problem, but I'm specifically looking if this problem could be solved using alias_method. Trying to learn.
Following code will work
class User
attr_accessor :name
alias_method :foo, :name=
end
u = User.new
u.foo('john')
puts u.name
However this code will not work for obvious reason.
class User
attr_accessor :name
alias_method :name, :name=
end
u = User.new
u.name('john')
puts u.name
Anyone knows if this problem can be fixed with alias_method.
I don't really get what you're trying to do. By writing attr_acccessor :name, the name= method gets created automatically. If you're trying to make this work:
user.name("john")
you can add this method:
class User
attr_accessor :name
def name(n)
self.name = n
end
end
The short version is alias method cannot help you here.
It looks like you're trying to merge the getter and setter created by attr_accessor. The reason this isn't working is because you're overriding the getter with the setter when you call alias.
To get the functionality you're looking for you'll have to define your own method.
Something like this:
class User
attr_accessor :name
def name(set_value = "VALUE_THAT_WILL_NEVER_BE_USED_AS_A_NAME")
unless set_value == "VALUE_THAT_WILL_NEVER_BE_USED_AS_A_NAME"
#name = set_value
end
#name
end
end
u = User.new
u.name('john') #=> sets name to john
puts u.name #=> puts john
# bonus! doesn't override name=
u.name = 'mike' # => sets name to mike
puts u.name #=> puts mike
Edit: Corrected method to allow setting of nil or false.
If you want to use a single getter/setter method beware of using "nil" since you may want to set your attribute to nil.
u.name("john")
u.name(nil) # name still will be john
So do something which checks that there are args
class U
attr_reader :n
def n(*args)
if(args.length > 0)
#n = args.first
else
#n
end
end
end
u = U.new
u.n "j"
u.n # j
u.n nil
u.n # nil
class User
attr_accessor :name
end
u = User.new
u.name = 'john'
u.name('john')
In this code u.name would be the getter for name. It looks like you're trying to call it as a setter? Is that want you want to do? If that's the case then you wouldn't use attr_accessor, you'd need to write the getters and setters yourself.

Resources