Listening for Changes to a Class






Listening for Changes to a Class

Credit: Phil Tomson

Problem

You want to be notified when the definition of a class changes. You might want to keep track of new methods added to the class, or existing methods that get removed or undefined. Being notified when a module is mixed into a class can also be useful.

Solution

Define the class methods method_added, method_removed, and/or method_undefined. Whenever the class gets a method added, removed, or undefined, Ruby will pass its symbol into the appropriate callback method.

The following example prints a message whenever a method is added, removed, or undefined. If the method "important" is removed, undefined, or redefined, it throws an exception.

	class Tracker
	  def important
	    "This is an important method!"
	  end

	  def self.method_added(sym)
	    if sym == :important
	      raise 'The "important" method has been redefined!'
	    else
	      puts %{Method "#{sym}" was (re)defined.}
	    end
	  end

	  def self.method_removed(sym)
	    if sym == :important
	      raise 'The "important" method has been removed!'
	    else
	      puts %{Method "#{sym}" was removed.}
	   end
	  end

	  def self.method_undefined(sym)
	    if sym == :important
	      raise 'The "important" method has been undefined!'
	    else
	      puts %{Method "#{sym}" was removed.}
	    end
	  end
	end

If someone adds a method to the class, a message will be printed:

	class Tracker
	  def new_method
	    'This is a new method.'
	  end
	end
	# Method "new_method" was (re)defined.

Short of freezing the class, you can't prevent the important method from being removed, undefined, or redefined, but you can raise a stink (more precisely, an exception) if someone changes it:

	class Tracker
	  undef :important
	end
	# RuntimeError: The "important" method has been undefined!

Discussion

The class methods we've defined in the Tracker class (method_added, method_removed, and method_undefined) are hook methods. Some other piece of code (in this case, the Ruby interpreter) knows to call any methods by that name when certain conditions are met. The Module class defines these methods with empty bodies: by default, nothing special happens when a method is added, removed, or undefined.

Given the code above, we will not be notified if our tracker class later mixes in a module. We won't hear about the module itself, nor about the new methods that are available because of the module inclusion.

	class Tracker
	  include Enumerable
	end

	# Nothing!

Detecting module inclusion is trickier. Ruby provides a hook method Module#included, which is called on a module whenever it's mixed into a class. But we want the opposite: a hook method that's called on a particular class whenever it includes a module. Since Ruby doesn't provide a hook method for module inclusion, we must define our own. To do this, we'll need to change Module#include itself.

	class Module
	 alias_method :include_no_hook, :include
	 def include(*modules)
	   # Run the old implementation.
	   include_no_hook(*modules)

	   # Then run the hook.
	   modules.each do |mod|
	     self.include_hook mod
	   end
	 end

	 def include_hook
	   # Do nothing by default, just like Module#method_added et al.
	   # This method must be overridden in a subclass to do something useful.
	 end
	end

Now when a module is included into a class, Ruby will call that class's include_hook method. If we define a tracker#include_hook method, we can have Ruby notify us of inclusions:

	class Tracker
	  def self.include_hook(mod)
	    puts %{"#{mod}" was included in #{self}.}
	  end
	end

	class Tracker
	  include Enumerable
	end
	# "Enumerable" was included in Tracker.

See Also

  • Recipe 9.3, "Mixing in Class Methods," for more on the Module#included method

  • Recipe 10.13, "Undefining a Method," for the difference between removing and undefining a method



 Python   SQL   Java   php   Perl 
 game development   web development   internet   *nix   graphics   hardware 
 telecommunications   C++ 
 Flash   Active Directory   Windows