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