Ruby child class update parent class variable - ruby

I am trying to update the parent class variable from the child class. This is what I have
class Dog
def initialize
#breed = []
end
def show
puts "#{#first_name} of breed #{#breed}" ### <- Changed
end
end
class Lab < Dog
attr_reader :first_name, :i
def initialize
super ### <- Added
#first_name = "good dog"
#i = 1
end
def add
#breed << #i
#i += 1
end
end
As you can see, I have a #breed variable in the parent class, which the child class has access to. But when I update the #breed variable from the child class and call the same variable from the parent class it does not show the update values. How can I do that?
irb(main):172:0> d = Dog.new
=> #<Dog:0x00005607908130d8 #breed=[]>
irb(main):173:0> l = Lab.new
=> #<Lab:0x000056079081add8 #breed=[], #first_name="good dog", #i=1>
irb(main):174:0> d.show
of breed []
irb(main):175:0> l.show
good dog of breed []
irb(main):176:0> l.add
=> 2
irb(main):177:0> l.show # inherited variable got updated
good dog of breed [1]
irb(main):178:0> d.show # parent array did not get updated?
of breed []
irb(main):179:0>
Calling d.show returns an empty array, but calling l.show returns the filled array. I would like my parent class also return the filled array. Is this possible? What am I missing.

d and l in your example are absolutely different objects, having nothing in common. That’s not how inheritance works.
There would be a brief example of using the instance variable from parent class.
class Dog
def initialize
#breed = []
end
def add
puts "Dog: #{#breed}"
end
end
class Lab < Dog
def initialize
super ### <- #breed is now declared
end
def add
puts "Lab 1: #{#breed}"
#breed << :ok
super
puts "Lab 2: #{#breed}"
end
end
Lab.new.add
#⇒ Lab 1: []
#⇒ Dog: [:ok]
#⇒ Lab 2: [:ok]

No, this is not possible, at least not without some (unadvisable) hacking.
You are missing the distinction between classes and instances. There can be a hierarchy of classes, as you created with Dog and Lab, but the instances you create from these classes don't have a hierarchical relationship. The (instance) variables, like #breed, are private to each instance and each instance has its own copies.
In other words: There is no "parent array", only an array which has been declared in a parent class.
It looks like you want a collection of Dog instances. The right place would a variable outside of the Dog class.
class Dog
attr_reader :first_name
def initialize(name)
#first_name = name
end
def show
puts "#{first_name} of breed #{self.class.name}"
end
end
class Lab < Dog
def initialize
super('good dog')
end
end
class Schnauzer < Dog
end
class BlackLab < Lab
end
Notice that I moved first_name from Lab to Dog, because Dog#show already uses it and it makes no sense to declare it in a sub class.
You can use this class hierarchy to collect multiple instances in an array which is stored outside of the class hierarchy:
breeds = []
breeds << Dog.new('some dog')
breeds << Lab.new
breeds << Dog.new('another dog')
breeds << Schnauzer.new('yet another')
breeds << BlackLab.new
breeds.each do |dog_instance|
dog_instance.show
end
This produces the following output:
some dog of breed Dog
good dog of breed Lab
another dog of breed Dog
yet another of breed Schnauzer
good dog of breed BlackLab

class Dog
def initialize
puts "self in Dog#initialize = #{self}"
#breed = []
end
def show
puts "#{#first_name} of breed #{#breed}"
end
end
class Lab < Dog
attr_reader :first_name, :i
def initialize
super
#first_name = "good dog"
#i = 1
end
def add
#breed << #i
#i += 1
end
end
Let's create an instance of Lab and examine its instance variables:
sue = Lab.new
#=> #<Lab:0x000059195710eec8 #breed=[], #first_name="good dog", #i=1>
# displays: self in Dog#initialize = #<Lab:0x000059195710eec8>
sue.instance_variables
#=> [:#breed, :#first_name, :#i]
sue.first_name"
#=> "good dog"
sue.i
#=> 1
sue.instance_variable_get(:#breed)
#=> []
Notice I had to use Object#instance_variable_get to obtain the value of sue's instance variable #breed because no getter had been created for that instance variable.
You say you wish to "update the parent class variable from the child class". Firstly the parent class, Dog has no class variables or instance variables (sometimes referred to as class instance variables). I assume you mean the instance variable #breed that is associated with each instance of Dog. In fact, it appears that one would not likely create an instance of Dog; that Dog has been created only to define methods and instance variables that are to be used by subclasses of Dog. Nevertheless, let's create an instance of Dog:
bob = Dog.new
#=> #<Dog:0x0000591956ffd4a8 #breed=[]>
# displays: self in Dog#initialize = #<Dog:0x0000591956ffd4a8>
bob.instance_variables
#=> [:#breed]
bob.instance_variable_get(:#breed)
#=> []
Let's also change the value of #breed for this instance:
bob.instance_variable_get(:#breed) << "pug"
#=> ["pug"]
Now let's add a method get_iv to Lab to get the value of the instance variable #breed of an instance of Dog. Think about the information that must be passed to this method. It needs to know which instance of Dog we are concerned with (as different instances may very well have different values for their instance variables), and for that instance, which instance variable we are interested in. (Here there is but one, but let's make it general so that we could add instance variables to Dog's instances.)
class Lab
def get_iv(instance, instance_variable)
instance.public_send(:instance_variable_get, instance_variable)
end
end
See Object#public_send. So we could now write:
sue.get_iv(bob, :#breed)
#=> ["pug"]
sue.get_iv(bob, :#breed) << "collie"
#=> ["pug", "collie"]
Sure enough, we have accomplished the task:
bob.instance_variable_get(:#breed)
#=> ["pug", "collie"]
But wait! Now let's create a completely unrelated class and an instance of it.
class Bird
def initialize
#type = ["canary"]
end
end
tweetie = Bird.new
#=> #<Bird:0x0000591956ffee98 #type=["canary"]>
Next, let sue get and alter the value of tweetie's instance variable #type:
sue.get_iv(tweetie, :#type)
#=> ["canary"]
sue.get_iv(tweetie, :#type) << "robin"
#=> ["canary", "robin"]
and confirm they've been changed:
tweetie.instance_variable_get(:#breed)
#=> ["canary", "robin"]
The point is that the instance method we must add to Lab requires two pieces of information:
the instance whose instance variable's value is to be obtained; and
the name of the instance variable.
This has nothing to do with the fact that Lab is a subclass of Dog! Therefore, to get or set the value of an instance variable of an instance of Lab's superclass is no different than getting or setting the value of an instance variable of any other class.

Related

Getting object name in ruby

class Animal
def initialize(noise)
#noise = noise
end
def say
puts "#{self.class} does #{#noise}"
end
end
dog = Animal.new("Woof Woof")
cat = Animal.new("Meow Meow")
dog.say
cat.say
How can I print dog does woof woof instead of Animal does Woof Woof?
To be clear, Animal is the name of the class you have created. That is why when you
puts " #{self.class} etc... "
the text Animal appears.
Now in your example, cat and dog are the names of variables. These are nothing more than references to the objects you create in the lines:
dog = Animal.new("Woof Woof")
cat = Animal.new("Meow Meow")
The variable may be called dog or cat, but the class remains Animal. To my knowledge, there is no good way to get name of a variable, short of some really convoluted introspection tools.
On the other hand: If you simply want your animals to have names, consider adding a name attribute.
class Animal
def initialize(name, noise)
#name = name
#noise = noise
end
def say
puts "#{#name} does #{#noise}"
end
end
dog = Animal.new("Dog", "Woof Woof")
cat = Animal.new("Cat", "Meow Meow")
dog.say
cat.say
This is a code misunderstanding, no big deal. Animal IS the object, that's why it will always print Animal does Woof Woof. You know that Animal is the object because you've declared it: class Animal, a class is an object.
dog is an instance of that Animal class. If you want to print the instance name, then you need to actually create an attribute, just like you've done with #noise. So, one example might be:
def initialize(noise, name)
#noise = noise
#name = name
end
dog = Animal.new("Woof Woof", "dog")
Note you'll probably want to not pass params individually but in a params hash, so it becomes clearer to read.

Avoiding initialize method in ruby

I am writing the Ruby program found below
class Animal
attr_reader :name, :age
def name=(value)
if value == ""
raise "Name can't be blank!"
end
#name = value
end
def age=(value)
if value < 0
raise "An age of #{value} isn't valid!"
end
#age = value
end
def talk
puts "#{#name} says Bark!"
end
def move(destination)
puts "#{#name} runs to the #{destination}."
end
def report_age
puts "#{#name} is #{#age} years old."
end
end
class Dog < Animal
end
class Bird < Animal
end
class Cat < Animal
end
whiskers = Cat.new("Whiskers")
fido = Dog.new("Fido")
polly = Bird.new("Polly")
polly.age = 2
polly.report_age
fido.move("yard")
whiskers.talk
But when I run it, it gives this error:
C:/Users/akathaku/mars2/LearningRuby/Animal.rb:32:in `initialize': wrong number of arguments (1 for 0) (ArgumentError)
from C:/Users/akathaku/mars2/LearningRuby/Animal.rb:32:in `new'
from C:/Users/akathaku/mars2/LearningRuby/Animal.rb:32:in `<main>'
My investigations shows that I should create objects like this
whiskers = Cat.new("Whiskers")
Then there should be an initialize method in my code which will initialize the instance variable with the value "Whiskers".
But if I do so then what is the purpose of attribute accessors that I am using? Or is it like that we can use only one and if I have to use attribute accessors then I should avoid initializing the instance variables during object creation.
initialize is the constructor of your class and it runs when objects are created.
Attribute accessors are used to read or modify attributes of existing objects.
Parameterizing the constructor(s) gives you the advantage of having a short and neat way to give values to your object's properties.
whiskers = Cat.new("Whiskers")
looks better and it's easier to write than
whiskers = Cat.new
whiskers.name = "Whiskers"
The code for initialize in this case should look like
class Animal
...
def initialize(a_name)
name = a_name
end
...
end
All attr_reader :foo does is define the method def foo; #foo; end. Likewise, attr_writer :foo does so for def foo=(val); #foo = val; end. They do not do assume anything about how you want to structure your initialize method, and you would have to add something like
def initialize(foo)
#foo = foo
end
Though, if you want to reduce boilerplate code for attributes, you can use something like Struct or Virtus.
You should define a method right below your class name, something like
def initialize name, age
#name = name
#age = age
end

how to get child class variable back into parent class in ruby

I would like to create a base class to handle all the methods. How would I achieve this using ruby in this scenario?
class Dog
def initialize
#breed = "good breed"
end
def show_dog
puts "#{self.class.first_name} of breed #{#breed}"
end
end
class Lab < Dog
attr_reader :first_name
def initialize
#first_name = "good dog"
end
end
lb = Lab.new()
lb.show_dog
The expected result would be "good dog of breed good breed"
Thank you in advance :)
self.class.first_name doesn't do what you probably wanted to do. You need to use #first_name directly.
You need to call the parent class' constructor from the child class; it's not called automatically.
This works:
class Dog
def initialize
#breed = "good breed"
end
def show_dog
puts "#{#first_name} of breed #{#breed}" ### <- Changed
end
end
class Lab < Dog
attr_reader :first_name
def initialize
super ### <- Added
#first_name = "good dog"
end
end
lb = Lab.new()
lb.show_dog
self.class.first_name means Lab.first_name, not Lab's instance lb.first_name

Ruby: Namespacing of instance methods

I have TONS of instance methods, all sharing the same instance variables.
Since the class became huge, I split the methods into around 50 modules.
And then the class that is left is including all those 50 modules.
I ended up with an unbelievable ugly code which is full of instance methods like "module_name_method_name" to avoid collisions of method names.
The problem is that each Module might have similar (similar, not same) functionality (in turn similar method names).
My current code looks like this:
The modules:
module Toyota
def toyota_method1;#foo...;end
def toyota_method2;#foo...;end
....
end
module Ford
def ford_method1;#foo...;end
def ford_method2;#foo...;end
....
end
.... around 50 such modules
class Cars
include Toyota
include Ford
include ...
def foo
#foo = "bar"
#bar = "foo"
...
toyota_method1
ford_method2
toyota_method2
...
end
end
How could I design my code better?
The most important thing is that ALL instance methods need to share the same instance variables.. or at least somehow have access to the same data!
EDIT:
I just came up with this myself:
class Toyota
attr_accessor :foo
def method1
puts #foo
end
end
class Ford
attr_accessor :foo
def method1
puts #foo
end
end
class Cars
def foo
#foo = "bar"
toyota = Toyota.new
toyota.foo = #foo
toyota.method1
ford = Ford.new
ford.foo = #foo
ford.method1
end
end
cars = Cars.new
cars.foo
In fact it solves the ugly method name problem, but now I'm dealing with new problems: the variable #foo might be quite large and it's duplicating itself 50 times (or more) into memory (since I have 50 such classes).
Any other solutions?
It's not only duplicating 50 times or more, its duplicated per instance since it's an instance variable. From what I can tell about your code, and you have to correct me if I'm wrong, it would be best for you to use inheritance along with variable sharing on the class level. This may not work if your car class really needs to know all the different methods from all kind of models it might be, but then I would say you've done something inherently wrong from the beginning. So let's look at inheritance:
class Car
attr_accessor :foo
# lets you initialize attributes via a hash
#
def initialize attrs={}
super()
attrs.each do |att, value|
self.send(:"#{att}=", value)
end
end
end
Inherit Toyota from Car:
class Toyota < Car
def method1
# do what you want to do in this method
end
end
Same for Ford:
class Ford < Car
def method1
# do what you want to do in this method
end
end
Like this you won't have to put namespaces in front of your method:
Ford.new(:foo => 'fordfoo').foo #=> will put out «fordfoo»
Toyota.new(:foo => 'toyotafoo').foo #=> will put out «toyotafoo»
Sharing across classes
Now, if foo is Shared across all cars, you can make it either a) a constant on the car class if it is static:
class Car
FOO = 'bar'
def put_foo
puts FOO
end
end
Like this, all Instances of Car, Toyota or Ford will have access to the static constant FOO, which will only exist one time in memory:
Toyota.new.put_foo #=> 'bar'
If foo has to be assignable and only has to exist one time for the whole inheritance tree, use class variables:
class Car
class << self
def foo= data
##foo = data
end
end
def foo= data
self.class.foo = data
end
def foo
##foo
end
end
Now ##foo only exists once for the inheritance tree, so if you do this:
car = Car.new
toyota = Toyota.new
toyota.foo = 'toyota'
car.foo #=> 'toyota'
This can lead to serious problems if you're not paying attention, but if foo must be the same anyway for all classes, it's the way to go. There are also some issues with Thread Safety you will have to address (with Mutex) working with class variables.
If Car, Toyota and Ford all have different foo, but the instances of each class need to share the same foo (so it is 3 foo's in total), use class instance variables:
class Car
class << self
def foo= data
#foo = data
end
def foo
#foo
end
end
def foo= data
self.class.foo = data
end
def foo
self.class.foo
end
end
Like this you will get:
car = Car.new
car.foo = 'bla'
toyota = Toyota.new
toyota.foo #=> is nil
toyota.foo = 'bar'
car.foo #=> still 'bla'
toyota.foo #=> is 'bar'
These are the ways to share the same data across instances.

What does ##variable mean in Ruby?

What are Ruby variables preceded with double at signs (##)? My understanding of a variable preceded with an at sign is that it is an instance variable, like this in PHP:
PHP version
class Person {
public $name;
public function setName($name) {
$this->name = $name;
}
public function getName() {
return $this->name;
}
}
Ruby equivalent
class Person
def set_name(name)
#name = name
end
def get_name()
#name
end
end
What does the double at sign ## mean, and how does it differ from a single at sign?
A variable prefixed with # is an instance variable, while one prefixed with ## is a class variable. Check out the following example; its output is in the comments at the end of the puts lines:
class Test
##shared = 1
def value
##shared
end
def value=(value)
##shared = value
end
end
class AnotherTest < Test; end
t = Test.new
puts "t.value is #{t.value}" # 1
t.value = 2
puts "t.value is #{t.value}" # 2
x = Test.new
puts "x.value is #{x.value}" # 2
a = AnotherTest.new
puts "a.value is #{a.value}" # 2
a.value = 3
puts "a.value is #{a.value}" # 3
puts "t.value is #{t.value}" # 3
puts "x.value is #{x.value}" # 3
You can see that ##shared is shared between the classes; setting the value in an instance of one changes the value for all other instances of that class and even child classes, where a variable named #shared, with one #, would not be.
[Update]
As Phrogz mentions in the comments, it's a common idiom in Ruby to track class-level data with an instance variable on the class itself. This can be a tricky subject to wrap your mind around, and there is plenty of additional reading on the subject, but think about it as modifying the Class class, but only the instance of the Class class you're working with. An example:
class Polygon
class << self
attr_accessor :sides
end
end
class Triangle < Polygon
#sides = 3
end
class Rectangle < Polygon
#sides = 4
end
class Square < Rectangle
end
class Hexagon < Polygon
#sides = 6
end
puts "Triangle.sides: #{Triangle.sides.inspect}" # 3
puts "Rectangle.sides: #{Rectangle.sides.inspect}" # 4
puts "Square.sides: #{Square.sides.inspect}" # nil
puts "Hexagon.sides: #{Hexagon.sides.inspect}" # 6
I included the Square example (which outputs nil) to demonstrate that this may not behave 100% as you expect; the article I linked above has plenty of additional information on the subject.
Also keep in mind that, as with most data, you should be extremely careful with class variables in a multithreaded environment, as per dmarkow's comment.
# - Instance variable of a class
## - Class variable, also called as static variable in some cases
A class variable is a variable that is shared amongst all instances of a class. This means that only one variable value exists for all objects instantiated from this class. If one object instance changes the value of the variable, that new value will essentially change for all other object instances.
Another way of thinking of thinking of class variables is as global variables within the context of a single class.
Class variables are declared by prefixing the variable name with two # characters (##). Class variables must be initialized at creation time
## denotes a class variable, i.e. it can be inherited.
This means that if you create a subclass of that class, it will inherit the variable. So if you have a class Vehicle with the class variable ##number_of_wheels then if you create a class Car < Vehicle then it too will have the class variable ##number_of_wheels
The answers are partially correct because ## is actually a class variable which is per class hierarchy meaning it is shared by a class, its instances and its descendant classes and their instances.
class Person
##people = []
def initialize
##people << self
end
def self.people
##people
end
end
class Student < Person
end
class Graduate < Student
end
Person.new
Student.new
puts Graduate.people
This will output
#<Person:0x007fa70fa24870>
#<Student:0x007fa70fa24848>
So there is only one same ##variable for Person, Student and Graduate classes and all class and instance methods of these classes refer to the same variable.
There is another way of defining a class variable which is defined on a class object (Remember that each class is actually an instance of something which is actually the Class class but it is another story). You use # notation instead of ## but you can't access these variables from instance methods. You need to have class method wrappers.
class Person
def initialize
self.class.add_person self
end
def self.people
#people
end
def self.add_person instance
#people ||= []
#people << instance
end
end
class Student < Person
end
class Graduate < Student
end
Person.new
Person.new
Student.new
Student.new
Graduate.new
Graduate.new
puts Student.people.join(",")
puts Person.people.join(",")
puts Graduate.people.join(",")
Here, #people is single per class instead of class hierarchy because it is actually a variable stored on each class instance. This is the output:
#<Student:0x007f8e9d2267e8>,#<Student:0x007f8e9d21ff38>
#<Person:0x007f8e9d226158>,#<Person:0x007f8e9d226608>
#<Graduate:0x007f8e9d21fec0>,#<Graduate:0x007f8e9d21fdf8>
One important difference is that, you cannot access these class variables (or class instance variables you can say) directly from instance methods because #people in an instance method would refer to an instance variable of that specific instance of the Person or Student or Graduate classes.
So while other answers correctly state that #myvariable (with single # notation) is always an instance variable, it doesn't necessarily mean that it is not a single shared variable for all instances of that class.
# and ## in modules also work differently when a class extends or includes that module.
So given
module A
#a = 'module'
##a = 'module'
def get1
#a
end
def get2
##a
end
def set1(a)
#a = a
end
def set2(a)
##a = a
end
def self.set1(a)
#a = a
end
def self.set2(a)
##a = a
end
end
Then you get the outputs below shown as comments
class X
extend A
puts get1.inspect # nil
puts get2.inspect # "module"
#a = 'class'
##a = 'class'
puts get1.inspect # "class"
puts get2.inspect # "module"
set1('set')
set2('set')
puts get1.inspect # "set"
puts get2.inspect # "set"
A.set1('sset')
A.set2('sset')
puts get1.inspect # "set"
puts get2.inspect # "sset"
end
class Y
include A
def doit
puts get1.inspect # nil
puts get2.inspect # "module"
#a = 'class'
##a = 'class'
puts get1.inspect # "class"
puts get2.inspect # "class"
set1('set')
set2('set')
puts get1.inspect # "set"
puts get2.inspect # "set"
A.set1('sset')
A.set2('sset')
puts get1.inspect # "set"
puts get2.inspect # "sset"
end
end
Y.new.doit
So use ## in modules for variables you want common to all their uses, and use # in modules for variables you want separate for every use context.

Resources