rubyattr-accessor

What happens when attr_accessor is within a class method?


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=]

Solution

  • 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