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.
Related
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.
The following code is an extract from rubykoans about_classes.rb:
class Dog7
def initialize(initial_name)
#name = initial_name
end
def to_s
#name
end
end
I created an instance of Dog7:
fido = Dog7.new("Fido")
I understand the following:
"My dog is " + fido.to_s # => "My dog is Fido"
"My dog is #{fido.to_s}" # => "My dog is Fido"
I do not understand why the following interpolation makes sense and gives the same result:
"My dog is #{fido}" # => "My dog is Fido"
fido is not a string.
The statement #{fido} implicitly calls fido.to_s. That's why you get the "Fido", which is the value of #name.
Actually, "My dog is #{fido.to_s}" is redundant, because the #{} bit will call to_s.
Here is another way of formatting strings:
"My dog is %s" % fido
This is pretty much another version of the #{} syntax. Above, the %s indicates to the formatter that it needs to call to_s on fido. It would be redundant to do "My dog is %s" % fido.to_s, however, it would still work.
Well, I can do this:
class Person
attr_accessor :name
def greeting
"Hello #{#name}"
end
end
p = Person.new
p.name = 'Dave'
p.greeting # "Hello Dave"
but when I decide to assign the property in the class itself it doesnt work:
class Person
attr_accessor :name
#name = "Dave"
def greeting
"Hello #{#name}"
end
end
p = Person.new
p.greeting # "Hello"
This is the default behavior, albeit a confusing one (especially if you're used to other languages in the OOP region).
Instance variables in Ruby starts being available when it is assigned to and normally this happens in the initialize method of your class.
class Person
def initialize(name)
#name = name
end
end
In your examples you're using attr_accessor, this magical method produces a getter and a setter for the property name. A Person#name and Person#name=, method is created which overrides your "inline" instance variable (that's why your first example works and the second one doesn't).
So the proper way to write your expected behaviour would be with the use of a initialize method.
class Person
def initialize(name)
#name = name
end
def greeting
"Hello, #{#name}"
end
end
Edit
After a bit of searching I found this awesome answer, all rep should go to that question.
Think of a Person class as a blueprint that you can create single person instances with. As not all of these person instances will have the name "Dave", you should set this name on the instance itself.
class Person
def initialize(name)
#name = name
end
attr_accessor :name
def greeting
"Hello #{#name}"
end
end
david = Person.new("David")
p david.greeting
# => "Hello David"
mike = Person.new("Mike")
p mike.greeting
# => "Hello Mike"
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
For example:
class Animal
def make_noise
print NOISE
end
end
class Dog < Animal
NOISE = "bark"
end
d = Dog.new
d.make_noise # I want this to print "bark"
How do I accomplish the above? Currently it says
uninitialized constant Animal::NOISE
I think that you don't really want a constant; I think that you want an instance variable on the class:
class Animal
#noise = "whaargarble"
class << self
attr_accessor :noise
end
def make_noise
puts self.class.noise
end
end
class Dog < Animal
#noise = "bark"
end
a = Animal.new
d = Dog.new
a.make_noise #=> "whaargarble"
d.make_noise #=> "bark"
Dog.noise = "WOOF"
d.make_noise #=> "WOOF"
a.make_noise #=> "whaargarble"
However, if you are sure that you want a constant:
class Animal
def make_noise
puts self.class::NOISE
# or self.class.const_get(:NOISE)
end
end
one way to do it without class instance variables:
class Animal
def make_noise
print self.class::NOISE
end
end
class Dog < Animal
NOISE = "bark"
end
d = Dog.new
d.make_noise # prints bark
I think you have the wrong concept here. Classes in Ruby are similar to classes in Java, Smalltalk, C#, ... and all are templates for their instances. So the class defines the structure and the behavior if its instances, and the parts of the structure and behavior of the instances of its subclasses but not vice versae.
So direct access from a superclass to a constant in a subclass is not possible at all, and that is a good thing. See below how to fix it. For your classes defined, the following things are true:
class Animal defines the method make_noise.
instances of class Animal may call the method make_noise.
class Dogdefines the constant NOISE with its value.
instances of Dog and the class Dog itself may use the constant NOISE.
What is not possible:
Instances of Animal or the class Animal itself have access to constants of the class Dog.
You may fix that by the following change:
class Animal
def make_noise
print Dog::NOISE
end
end
But this is bad style, because now, your superclass (which is an abstraction about Dog and other animals) knows now something that belongs to Dog.
A better solution would be:
Define an abstract method in class Animal which defines that make_noise should be defined. See the answer https://stackoverflow.com/a/6792499/41540.
Define in your concrete classes the method again, but now with the reference to the constant.
If you're doing this to configure your sub classes so that the base class has access to the constants then you can create a DSL for them like this:
module KlassConfig
def attr_config(attribute)
define_singleton_method(attribute) do |*args|
method_name = "config_#{attribute}"
define_singleton_method method_name do
args.first
end
define_method method_name do
args.first
end
end
end
end
class Animal
extend KlassConfig
attr_config :noise
def make_noise
puts config_noise
end
end
class Dog < Animal
noise 'bark'
end
This way is a bit more performant as on each method call you don't have to introspect the class to reach back (or is it forward?) for the constant.
If you want it the Object-Oriented Way (TM), then I guess you want:
class Animal
# abstract animals cannot make a noise
end
class Dog < Animal
def make_noise
print "bark"
end
end
class Cat < Animal
def make_noise
print "meow"
end
end
d = Dog.new
d.make_noise # prints bark
c = Cat.new
c.make_noise # prints meow
If you want to refactor to prevent duplicating the code for print:
class Animal
def make_noise
print noise
end
end
class Dog < Animal
def noise
"bark"
end
end
class Cat < Animal
def noise
if friendly
"meow"
else
"hiss"
end
end
end
d = Dog.new
d.make_noise # prints bark
c = Cat.new
c.make_noise # prints meow or hiss