Ruby Object Model clarification - ruby

In Ruby, I can write,
Dog = Class.new
so here, Dog is an Object which is an instance of Class.
Also, I can write
fido = Dog.new
which is possible only if Dog is a Class.
Is Dog here a Class or an Object?

Everything in ruby is an Object (except for blocks). And Dog here is also a Class.
Dog = Class.new
fido = Dog.new
So the answer is: both.

Ask the object itself, to know where they belongs to, like below:
Dog = Class.new
fido = Dog.new
Dog.instance_of? Class #=> true
fido.instance_of? Class #=> false
fido.instance_of? Dog #=> true
Dog.superclass #=> Object
Dog.is_a? Object #=> true
Dog.is_a? Class #=> true
To look into in more detail see the Object model Documentation

I think you are making a mistake that some beginners repeatedly do. You are confusing the two meanings of "is":
A is an instance of B, and
A is a subclass of B.
With your case,
Dog is an instance of (but not a subclass of) Class, and
Dog is a subclass of (but not an instance of) Object.
So, in different senses, it is a Class and is an Object.
When they say "everything in Ruby is an Object", it does not mean that everything is an instance of Object. It means that everything is an instance of a (reflexive) subclass of Object.

First, install 'y_support' by typing gem install y_support in your command prompt. Then, in irb:
require 'y_support/name_magic'
class Animal
include NameMagic
end # You have created a new class
Animal.name #=> "Animal" -- the class is named Animal
class Dog < Animal
def speak; puts "Bow wow!" end
end #=> hereby, you have created a subclass of Animal class
Cat = Class.new( Animal ) do
def speak; puts "Meow!" end
end #=> this is another way of creating a subclass
Dog.name #=> "Dog" -- this is a class named Dog
And now
Fido = Dog.new #=> You have created a new Dog instance
Dog.instance_names #=> [:Fido] -- the instance is named Fido
Stripes = Cat.new #=> You have created a new Cat instance
Cat.instance_names #=> [:Stripes] -- the instance is named Cat
Animal.instances.size #=> 2 -- we have 2 animals thus far
Animal.instances.each do |animal| animal.speak end #=> Bow wow! Meow!
Let's create another dog:
Spot = Dog.new #=> Another Dog instance
Dog.instances.size #=> 2 -- we now have 2 dogs
Fido.class #=> Dog -- Fido is an instance of class Dog
Spot.class #=> Dog -- Spot is also an instance of class Dog
Fido.class.ancestors #=> The output shows you that Fido is also an Animal
Animal.instances.size #=> 3 -- together, we have 3 animals
Animal.instance_names #=> [:Fido, :Stripes, :Spot]
Animal.instance( :Fido ).speak #=> Bow wow!
Animal.instances.each &:speak #=> Bow wow! Meow! Bow wow!
Understood? Remember, in Ruby, never work without NameMagic.

Related

Ruby child class update parent class variable

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.

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.

Share one variable amongst several objects

class Collie
def speak
puts dog_generic
end
end
class Greyhound
def speak
puts dog_generic
end
end
class Labrador
def speak
puts dog_generic
end
end
dog_generic = "Woof"
chep = Collie.new
wrex = Collie.new
speedy = Greyhound.new
faithful = Labrador.new
chep.speak #=> Woof
wrex.speak #=> Woof
speedy.speak #=> Woof
faithful.speak #=> Woof
I'd like those last three methods to all return "Woof". However, this code will call an undefined variable dog_generic error. This seems to be because even global variables aren't available to objects. If I were to change all instances of dog_generic to ##dog_generic, it would work, but ## variables are rarely used, and based on that alone, I can't help thinking I'd be doing it wrong.
How can I share one variable amongst several objects?
And no, I don't want to pass in a string of "Woof" to every single object as a parameter.
Typically, one would use inheritance to provide this sort of behavior:
class Dog
def speak
puts "Woof"
end
end
class Collie < Dog
# whatever behavior that is specific to Collie here
end
chep = Collie.new
chep.speak #=> Woof
You seem to have some confusion about what a global variable in ruby is. You have to explicitly make them global, with the $ sigil, a la
$dog_generic = 'Woof'
and
def speak
puts $dog_generic
end
That said, using a global is probably your worst approach here. Defining either a generic dog class and having your specific types inherit, or else creating a dog mixin and including it would both be better solutions IMHO.
Make a constant. Any variable starting with a capital letter is a constant. Including classes, so constants can be scoped globally. class Foo; end is a constant that points to a class, you could also write this as Foo = Class.new.
class Collie
def speak
puts DOG_GENERIC
end
end
class Greyhound
def speak
puts DOG_GENERIC
end
end
class Labrador
def speak
puts DOG_GENERIC
end
end
DOG_GENERIC = "Woof"
chep = Collie.new
wrex = Collie.new
speedy = Greyhound.new
faithful = Labrador.new
chep.speak #=> Woof
wrex.speak #=> Woof
speedy.speak #=> Woof
faithful.speak #=> Woof
I agree with #perimosocordiae, you should probably use inheritance. I would disagree with him though on use of a class for inheritance. Modules in Ruby are included in the inheritance chain, however, they do not have constructors (you can think of them as abstract classes, but you can inherit multiple modules).
module Speach
WOOF = "Woof"
QUACK = "Quack"
module Dog
def speak
puts WOOF
end
end
module Duck
def speak
puts QUACK
end
end
end
class Collie
include Speach::Dog
end
class Greyhound
include Speach::Dog
# can overwrite ancestor
def speak
puts "Ruff"
end
end
class Mallard
include Speach::Duck
end
Collie.new.speak # => "Woof"
Greyhound.new.speak # => "Ruff"
Mallard.new.speak # => "Quack"
While others answered your question about subclassing Dog, I would like to introduce you to a better way of naming your instances: NameMagic. Type gem install y_support in your command line, and require NameMagic it by:
require 'y_support/name_magic'
And then:
class Dog
include NameMagic
def speak
puts "Woof"
end
end
Collie = Class.new Dog
Greyhound = Class.new Dog
Labrador = Class.new Dog
Chep = Collie.new
Wrex = Collie.new
Speedy = Greyhound.new
Faithful = Labrador.new
And then:
Dog.instances.each &:speak
#=> Woof
#=> Woof
#=> Woof
#=> Woof
NameMagic also allows you to query instances and their names:
Dog.instances
#=> [#<Collie:0xb90e3c38>, #<Collie:0xb90e1adc>, #<Greyhound:0xb90e7860>, #<Labrador:0xb90e5574>]
Collie.instances
#=> [#<Collie:0xb90e3c38>, #<Collie:0xb90e1adc>]
Labrador.instances
#=> [#<Labrador:0xb90e5574>]
Dog.instance_names
#=> [:Chep, :Wrex, :Speedy, :Faithful]
Collie.instance_names
#=> [:Chep, :Wrex]

Confusion with instance method calls of super class from subclass

I was just playing to see the instance method calls from the subclass and used the below code of my test:
class Animal
def bark
p "hukkhh"
end
end
#=> nil
class Cow < Animal
end
#=> nil
Cow.public_instance_method(:bark)
#=> #<UnboundMethod: Cow(Animal)#bark>
class Cow
bark
end
#=> NameError: undefined local variable or method `bark' for Cow:Class
# from (irb):11:in `<class:Cow>'
# from (irb):10
# from C:/Ruby193/bin/irb:12:in `<main>'
From that code I was confirmed that instance method can't be executed without the object instance of the respective class.
But then I tried below code:
def talk
p "hi"
end
#=> nil
Object.public_instance_method(:talk)
#=> #<UnboundMethod: Object#talk>
class Foo
talk
end
# prints: hi
#=> "hi"
Here the output made me confused with my first test code output.
Could anyone help me to understand the fact behind these above?
Method talk you defined is belongs to Object which is root of all objects. So talk method is available to all object in ruby.
def talk
p "hi"
end
Object.respond_to? :talk #=> true
Now, you defined class Foo which is also an ruby object.
Foo.is_a? Object #=> true
Foo.respond_to? :talk #=> ture
So talk is available to Foo class.
Now you define a class Animal with method bark
class Animal
def bark
p "hukkhh"
end
end
bark method you define is belong to Animal class for now as instance level method not class level. It means you need to create instance of class Animal to call bark.
Animal.respond_to? :bark #=> false
Object.respond_to? :bark #=> false
Animal.new.respond_to? :bark #=> true
Animal.respond_to? :talk #=> true #talk is available to Animal also because Animal is also object of Class
Animal.new.respond_to? :talk #=> true
Now you create class called Cow inheriting from Animal. So bark is available to Cow unless it is overridden.
class Cow < Animal
end
Cow.respond_to? :bark #=> false
Cow.new.respond_to? :bark #=> true
Cow.respond_to? :talk #=> true
Cow.new.respond_to? :talk #=> true
Cow.new.bark #=> "hukkhh"
So to call bark you need to create instance of class.
If you want to call a method of parent which is overridden in child use super
class Cow < Animal
def bark
super
#do your stuff
end
end
Cow.new.bark #=> "hukkhh"

Have a parent class's method access the subclass's constants

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

Resources