method_added object life-cycle hook. Using method_added is no big deal for the experienced Rubyist, but there's a fair amount of stuff in there which isn't obvious and which took some effort on my part to understand. I also ended up with a clearer understanding of the concepts which underpin Ruby's class structure.If you are unfamiliar with Ruby terminology, a class method behaves in a manner similar to a static method in Java or C#. I also use the words metaclass and singleton interchangeably, but either way I'm referring to the anonymous class associated with every class in Ruby.
Modifying class
Class to detect the addition of methodsmethod_added falls into the category of introspective methods provided by Ruby itself, and is invoked whenever a method is added to a class. Here's an example - remember that in Ruby, a class is itself an object.class Class
  def one
    return 1
  end  
  
  def method_added(method_name)
    puts "#{method_name} added to #{self}"
  end
  
  def two
    return 2
  end  
end
class Hello
  def say_it
    return "Hello!"
  end
endOutput:
method_added added to Class
two added to Class
say_it added to Hello
As you can see, the addition of method
one isn't detected because method_added hasn't been defined. Once it has been defined, all other method additions are detected (including the addition of method_added itself!).Modifying specific classes to detect the addition of methods
It's rarely that you'd need to detect the addition of methods to every single class in the ObjectSpace. Repeating what we did in the previous example just for a single class demonstrates some of that non-obviousness I was talking about. Let's try to detect all method additions to class
Hello; the obvious solution (given below) unfortunately doesn't work.class Hello
  def method_added(method_name)
    puts "#{method_name} added to #{self}"
  end
  
  def say_it
    return "Hello!"
  end
end
puts Hello.new.say_itOutput:
Hello!
We would expect to see
say_it was added to Hello, but we don't. Let's take this step by step and figure out what's going on.As I mentioned earlier,
Hello is an instance of Class. We can create the class Hello by simply saying Hello = Class.new. Let's prove this:def Hello.some_class_method  # => uninitialized constant Hello (NameError)
  return "It worked!"
end  
puts Hello.some_class_methodHello
Hello = Class.new
def Hello.some_class_method
  return "It worked!"
end  
puts Hello.some_class_method   # => It worked!method_added to Class allowed us to detect methods added to Hello. Obviously, Hello inherited method_added in some manner, but not as an instance method or our example above would have worked too. Let's go back to the first example and do some poking around by adding a couple of lines at the end.class Class
  def method_added(method_name)
    puts "#{method_name} added to #{self}"
  end
end
class  Hello
  def say_it
    return "Hello!"
  end
end
puts Hello.new.say_it
puts (Hello.methods - Hello.instance_methods).grep(/added/)
Hello.method_added("manually_invoking")Output:
method_added added to Class
say_it added to Hello
Hello!
method_added
manually_invoking added to HelloThe first line,
(Hello.methods - Hello.instance_methods).grep(/added/) first gets a collection of all methods belonging to Hello from which it removes those which are Hello's instance methods. It then searches among what's left for methods with the word 'added' in the name.Simply put, we find that
method_added has surfaced as a static or class method on Hello. The very next (and final) line in the example verifies this by actually invoking method_added and sure enough we were right - method_added is indeed a class method of Hello.Let's test this theory now with a quick example.
class  Hello
  def self.method_added(method_name)
    puts "#{method_name} added to #{self}"
  end
  
  def say_it
    return "Hello!"
  end
end
puts Hello.new.say_itOutput:
say_it added to Hello
Hello!So there we go, add
method_added as a class method to detect the addition of methods to to that class.The questions now are
a) Why does this work this way?
b) How do we detect the addition of class methods?
Lets tackle them in order.
It's all in the singleton class
The answer lies in the metaclass or singleton class holding
Hello's meta-data (which includes its methods). Let's try to define how they relate to each other with a bit of code. I'm creating a singleton_class helper method in Object to get hold of an instance's singleton/meta class.class Object
  def singleton_class
    class << self;self;end;
  end
end
class  Hello
end
p Hello.singleton_class.class
p Hello.singleton_class
p Hello.new.singleton_class
p Hello.singleton_class.singleton_class
puts Hello.singleton_class.superclass == Hello.new.singleton_class.superclass.superclassOutput:
Class
#<Class:Hello>
#<Class:#<Hello:0x2924904>>
#<Class:#<Class:Hello>>
trueWe see that the singleton classes is of type
Class. We also see that Hello and instances of Hello have their own singletons (I know that's obvious, but I thought I'd mention it anyways).When you add a method to a class, it is added not to the class itself, but rather to its metaclass. Therefore,
method_added needs to be in the metaclass, which is precisely what happens when you define a class method. Just to illustrate the point that class methods are simple methods defined on the metaclass, here are the different ways in which you can define a class method:class Hello
  def Hello.class_method_one
    # ...
  end
  def self.class_method_two
    # ...
  end
  class << self
    def class_method_three
      # ...
    end
  end
endQuestion (b), 'How do we detect the addition of class methods?', has a simpler answer:
singleton_method_added. I got this answer from the internal ThoughtWorks dynamic languages list (specifically Carlos Villela and Ola Bini - thanks guys!). Use it exactly as you would method_addedclass Hello
  class << self
    def method_added(method_name)
      puts "#{method_name} added to #{self}"
    end
    def singleton_method_added(method_name)
      puts "#{method_name} added to #{self}"
    end
  end
end
class Hello
  def instance_method
    "Hey!"
  end
  def self.class_method
    "Dude"
  end
endOutput:
singleton_method_added added to Hello
instance_method added to Hello
class_method added to Hello
To Summarise
- method_addedis an object life-cycle hook invoked whenever a method is added to a class
- To listen for the addition of instance methods to a class, method_addedmust be added to that class' singleton/meta class, or, to put it another way, as a class method of the class.
- To listen for addition of class methods, singleton_method_addedmust be added to the class' singleton/meta class, just like we would withmethod_added
 
1 comment:
"When you add a method to a class, it is added not to the class itself, but rather to its metaclass. Therefore, method_added needs to be in the metaclass, which is precisely what happens when you define a class method."
You are confusing instance methods with class methods here. I believe the reason method_added is in the metaclass is because an instance of the class does not exist during class definition. And during class definition the ruby is executable, your example with puts shows this. So when "def" is executed only a class method could be executed since no instance exists.
Post a Comment