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