So this came up to my mind and wonder what happens when some thing below is done.
class Test
def self.abc
attr_accessor :John
end
end
object = Test.new
puts "before calling class method abc: #{object.class.instance_methods(false)}"
Test.abc
puts "after calling class method abc: #{object.class.instance_methods(false)}"
Here what I checked was, do the getter and setter methods get created in this way. And if so, are those instance methods or class methods. First I create a new object and then I view what are the instance methods of that object. After that in next line I run the class
method abc
and then again, I check for instance methods of object
. At that time only I can see the two methods John
and John=
. How does this happen ? How come a running of a class method dynamically add methods to already created objects?. Can somebody please explain me this.
The output of the code was:
before calling class method abc: []
after calling class method abc: [:John, :John=]
When in a class method, self
is the class itself. Therefore, the following are equivalent in what they ultimately do:
class Test
def self.abc
# `self` in this context is Test.
# This sends the message `:attr_accessor, :john` to `Test`
attr_accessor :john
end
end
class Test
# `self` in this context is Test.
# This sends the message `:attr_accessor, :john` to `Test`
attr_accessor :john
end
However, as you noted, Test::abc
isn't executed when the class is parsed, so attr_accessor
isn't called, and the instance methods aren't added. It is perfectly valid to do this at runtime, and in fact, is the basis of much of the metaprogramming performed in Rails.
Typically, if you expect to add accessors via a class method, you might call that class method after definition but still during class declaration:
class Test
def self.abc
attr_accessor :john
end
abc
end
This will actually run, and properly declare the accessors on the class!
As to your question:
How come a running of a class method dynamically add methods to already created objects?
This is because instantiating a class doesn't instantiate a "snapshot" of the class at the time of instantiation - it creates an object which delegates much of its functionality (including discovery of the instance methods on it) to the class associated with it. Note that it is possible to define new methods on an instance which don't extend back to the class, too:
class Test
attr_accessor :foo
end
t1 = Test.new
t2 = Test.new
Test.send(:define_method, :bar) {}
puts t1.respond_to? :foo # => true
puts t2.respond_to? :foo # => true
puts t1.respond_to? :bar # => true
puts t2.respond_to? :bar # => true
t1.define_singleton_method(:baz) {}
puts t1.respond_to? :baz # => true
puts t2.respond_to? :baz # => false
Firstly, note that in place of
object = Test.new
object.class.instance_methods(false)
you could simply write
Test.instance_methods(false)
so let's simplify:
puts "before calling class method abc: #{Test.instance_methods(false)}"
# (prints) []
Test.abc
puts "after calling class method abc: #{Test.instance_methods(false)}"
# (prints) [:John, :John=]
There's a short explanation of this behaviour, but also a longer one.
Short Explanation
Test.method(:attr_accessor)
#=> #<Method: Class(Module)#attr_accessor>
This return value, together with the fact that
Test.class #=> Class
tells us that attr_accessor
is an instance_method of Class
, and therefore a method of Test
. Let's confirm.
Class.instance_methods.include?(:attr_accessor)
#=> false
Whoops! (You were expecting => true
?). That can only mean that attr_accessor
is a protected or private instance method:
Class.protected_instance_methods.include?(:attr_accessor)
#=> false
Class.private_instance_methods.include?(:attr_accessor)
#=> true
Therefore
Class.private_instance_methods.include?(:attr_accessor)
#=> true
So attr_accessor
is simply a private instance method of Test
's class, Class
, making it a private method of Test
. The expression (from earlier)
Test.method(:attr_accessor)
#=> #<Method: Class(Module)#attr_accessor>
also tells us that attr_accessor
is defined in Class
's superclass Module
.
Class.superclass
#=> Module
Class.ancestors
#=> [Class, Module, Object, Kernel, BasicObject]
Class.instance_method(:attr_accessor).owner
#=> Module
Test.method(:attr_accessor).owner
#=> Module
Longer Explanation
The method Module#attr_accessor can be made available to the class Test
in one of two ways. This is the essence of Ruby's object model. (I have sidestepped the place of singleton class methods.)
1. Test inherits the method from its superclass
Test.superclass
#=> Object
Test.ancestors
#=> [Test, Object, Kernel, BasicObject]
Let's see.
Object.method(:attr_accessor)
#=> #<Method: Class(Module)#attr_accessor>
Object.public_methods.include?(:attr_accessor)
#=> false
Object.private_methods.include?(:attr_accessor)
#=> true
2. attr_accessor
is an instance method of Test
's class
Test.class
#=> Class
Class.private_instance_methods.include?(:attr_accessor)
#=> true
So Test
both inherits attr_accessor
from its superclass and has it available as an instance method of its class. This duality is often explained with a diagram such as this one.
Ruby first checks Test
's class, Class
, for the instance method attr_accessor
. where it will find it. (Had she not found it there she'd then look for the method (not instance method) in the superclass.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With