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


Strings

Numbers

Date and Time

Arrays

Hashes

Files and Directories

Code Blocks and Iteration

Objects and Classes8

Modules and Namespaces

Reflection and Metaprogramming

XML and HTML

Graphics and Other File Formats

Databases and Persistence

Internet Services

Web Development Ruby on Rails

Web Services and Distributed Programming

Testing, Debugging, Optimizing, and Documenting

Packaging and Distributing Software

Automating Tasks with Rake

Multitasking and Multithreading

User Interface

Extending Ruby with Other Languages

System Administration



Ruby Cookbook
Ruby Cookbook (Cookbooks (OReilly))
ISBN: 0596523696
EAN: 2147483647
Year: N/A
Pages: 399

Flylib.com © 2008-2020.
If you may any questions please contact us: flylib@qtcs.net