Tuesday, April 08, 2008
Extend modules instead of defining methods on a metaclass
In the entry Replace method_missing with dynamic method definitions I have the following example code.
The context of the example can be summarized as, you want to delegate from the instance all the public methods defined on the constructor argument.
Ali Aghareza pointed out to me that defining methods on the metaclass of an instance isn't the nicest thing to do. The problem with it is that you've made it much harder for anyone else to change the behavior of the instance.
Here's a more simplified example. The following code creates a new Object and defines the hello_world method on the Object instance.
This works fine; however, if someone wanted to change the way hello_world behaved, by defining the method on the metaclass you force them to make their change by redefining the method on the metaclass. The current solution does not allow you to extend modules and alter the behavior of the instance.
The following example demonstrates that extending a module does not change the behavior of an instance if the behavior has been defined on the metaclass.
A better solution is to change the behavior of the instance by extending modules instead of defining behavior on the metaclass.
Now that the behavior is defined on an ancestor instead of the metaclass you can change the behavior by extending another module.
This solution works fine for our simple example, but it can also be applied to our first (much more complicated) example, even without knowing how to define the module. In the case of the Decorator, you can simply define an anonymous module and immediately extend it.
subject.public_methods(false).each do |meth|
(class << self; self; end).class_eval do
define_method meth do |*args|
subject.send meth, *args
end
end
end
end
endThe context of the example can be summarized as, you want to delegate from the instance all the public methods defined on the constructor argument.
Ali Aghareza pointed out to me that defining methods on the metaclass of an instance isn't the nicest thing to do. The problem with it is that you've made it much harder for anyone else to change the behavior of the instance.
Here's a more simplified example. The following code creates a new Object and defines the hello_world method on the Object instance.
; self; end
end
end
obj = Object.new
obj.metaclass.class_eval do
"hello"
end
end
obj.hello_world # => "hello"This works fine; however, if someone wanted to change the way hello_world behaved, by defining the method on the metaclass you force them to make their change by redefining the method on the metaclass. The current solution does not allow you to extend modules and alter the behavior of the instance.
The following example demonstrates that extending a module does not change the behavior of an instance if the behavior has been defined on the metaclass.
; self; end
end
end
obj = Object.new
obj.metaclass.class_eval do
"hello"
end
end
obj.hello_world # => "hello"
"hola"
end
end
obj.extend Spanish
obj.hello_world # => "hello"
A better solution is to change the behavior of the instance by extending modules instead of defining behavior on the metaclass.
obj = Object.new
"hello"
end
end
obj.extend(English).hello_world # => "hello"Now that the behavior is defined on an ancestor instead of the metaclass you can change the behavior by extending another module.
obj = Object.new
"hello"
end
end
obj.extend(English).hello_world # => "hello"
"hola"
end
end
obj.extend(Spanish).hello_world # => "hola"This solution works fine for our simple example, but it can also be applied to our first (much more complicated) example, even without knowing how to define the module. In the case of the Decorator, you can simply define an anonymous module and immediately extend it.
mod = Module.new do
subject.public_methods(false).each do |meth|
define_method meth do |*args|
subject.send meth, *args
end
end
end
extend mod
end
endLabels: def, define_method, metaclass, metaprogramming
Monday, April 07, 2008
Alternatives for redefining methods
Ruby's open classes allow you define and redefine behavior pretty much at will; unfortunately, almost every option comes with caveats.
The example below is a gateway class that defines a process method. For the purposes of the example, assume that we need to redefine the process method on Gateway itself and call the original process method.* Also, assume that Gateway is not our class, so we cannot easily alter the original definition of process.
Solution 1: alias
The following example uses alias to redefine the process method.
The example above creates an alias (old_process) for the process method. With an alias in place you can redefine the process method to anything you want and call the old_process method using the alias. This is probably the easiest solution and the most commonly used solution.
Unfortunately, it's not without problem. First of all, if someone redefines old_process you will get unexpected behavior. Second of all, the old_process method is really nothing more than an artifact of the fact that you have no other way to refer to the original method definition. Lastly, if the code is loaded twice, an infinite loop is created that causes the always painful to see 'stack level too deep' error.
Solution 2: alias_method_chain
Like I said, solution 1 is the most popular way to redefine a method. In fact, it's so popular Rails defines the alias_method_chain method to encapsulate the pattern. From the Rails source above alias_method_chain:
Using alias_method_chain is nice because it's something familiar to many Rails developers. Unfortunately, it also suffers from the same problems as using alias on your own.
Solution 3: Close on an unbound method
The following code uses the class method "instance_method" to assign the "process" method (as an unbound method) to a local variable. The "process_method" local variable is in scope of the closure used to define the new process method, so it can be used within the process definition. Calling an unbound method is as simple as binding it to any instance of the class that it was unbound from and then using the call method.
I've always preferred this solution because it doesn't rely on artifact methods that may or may not collide with other method definitions. Also, if the code is loaded multiple times the behavior is altered multiple times, but I find that easier to diagnose than when my only clue is "stack level too deep".
Unfortunately, this solution is not without flaws. Firstly, it relies on the fact that define_method uses a closure and has access to the unbound method. Of course this also implies that you have a handle on anything else defined in the same context. As with any closure, it's possible to accidentally create a memory leak. Also, (in MRI) I'm told that define_method takes 3 times as long to execute when compared to def.
Solution 4: Extend a module that redefines the method and uses super
This solution relies on creating a module with the new behavior and extending an instance with the module. Since the module is extended from the instance it will be checked first for the method definition when "process" is called (because it's the first ancestor). Since the module is the first ancestor it can use super to execute the process method defined in Gateway (the second ancestor).
This solution is my favorite because I can use def and super and never worry about creating any memory leaks or artifact methods.
Of course, it assumes that you get the opportunity to extend instances of the class. However, I haven't found that to be a problematic requirement.
* There are generally other options such as delegation, defining hooks, etc. Often I find these to be cleaner solutions and try that route first. But, sometimes redefining a method cannot be avoided.
The example below is a gateway class that defines a process method. For the purposes of the example, assume that we need to redefine the process method on Gateway itself and call the original process method.* Also, assume that Gateway is not our class, so we cannot easily alter the original definition of process.
p "gateway processed document: "
end
end
Gateway.new.process("hello world")
# >> "gateway processed document: hello world"Solution 1: alias
The following example uses alias to redefine the process method.
alias old_process process
p "do something else"
old_process(document)
end
end
Gateway.new.process("hello world")
# >> "do something else"
# >> "gateway processed document: hello world"
The example above creates an alias (old_process) for the process method. With an alias in place you can redefine the process method to anything you want and call the old_process method using the alias. This is probably the easiest solution and the most commonly used solution.
Unfortunately, it's not without problem. First of all, if someone redefines old_process you will get unexpected behavior. Second of all, the old_process method is really nothing more than an artifact of the fact that you have no other way to refer to the original method definition. Lastly, if the code is loaded twice, an infinite loop is created that causes the always painful to see 'stack level too deep' error.
Solution 2: alias_method_chain
Like I said, solution 1 is the most popular way to redefine a method. In fact, it's so popular Rails defines the alias_method_chain method to encapsulate the pattern. From the Rails source above alias_method_chain:
Encapsulates the common pattern of:Using alias_method_chain we can define our Gateway as the example below.
#
# alias_method :foo_without_feature, :foo
# alias_method :foo, :foo_with_feature
#
# With this, you simply do:
#
# alias_method_chain :foo, :feature
#
# And both aliases are set up for you.
#
# Query and bang methods (foo?, foo!) keep the same punctuation:
#
# alias_method_chain :foo?, :feature
#
# is equivalent to
#
# alias_method :foo_without_feature?, :foo?
# alias_method :foo?, :foo_with_feature?
#
# so you can safely chain foo, foo?, and foo! with the same feature.
p "do something else"
process_without_logging(document)
end
alias_method_chain :process, :logging
end
Gateway.new.process("hello world")
# >> "do something else"
# >> "gateway processed document: hello world"
Using alias_method_chain is nice because it's something familiar to many Rails developers. Unfortunately, it also suffers from the same problems as using alias on your own.
Solution 3: Close on an unbound method
The following code uses the class method "instance_method" to assign the "process" method (as an unbound method) to a local variable. The "process_method" local variable is in scope of the closure used to define the new process method, so it can be used within the process definition. Calling an unbound method is as simple as binding it to any instance of the class that it was unbound from and then using the call method.
process_method = instance_method(:process)
define_method :process do |document|
p "do something else"
process_method.bind(self).call(document)
end
end
Gateway.new.process("hello world")
# >> "do something else"
# >> "gateway processed document: hello world"I've always preferred this solution because it doesn't rely on artifact methods that may or may not collide with other method definitions. Also, if the code is loaded multiple times the behavior is altered multiple times, but I find that easier to diagnose than when my only clue is "stack level too deep".
Unfortunately, this solution is not without flaws. Firstly, it relies on the fact that define_method uses a closure and has access to the unbound method. Of course this also implies that you have a handle on anything else defined in the same context. As with any closure, it's possible to accidentally create a memory leak. Also, (in MRI) I'm told that define_method takes 3 times as long to execute when compared to def.
Solution 4: Extend a module that redefines the method and uses super
This solution relies on creating a module with the new behavior and extending an instance with the module. Since the module is extended from the instance it will be checked first for the method definition when "process" is called (because it's the first ancestor). Since the module is the first ancestor it can use super to execute the process method defined in Gateway (the second ancestor).
p "do something else"
super
end
end
Gateway.new.extend(ProcessLogging).instance_eval("class << self; self; end").ancestors
# => [ProcessLogging, Gateway, Object, Kernel]
Gateway.new.extend(ProcessLogging).process("hello world")
# >> "do something else"
# >> "gateway processed document: hello world"This solution is my favorite because I can use def and super and never worry about creating any memory leaks or artifact methods.
Of course, it assumes that you get the opportunity to extend instances of the class. However, I haven't found that to be a problematic requirement.
* There are generally other options such as delegation, defining hooks, etc. Often I find these to be cleaner solutions and try that route first. But, sometimes redefining a method cannot be avoided.
Labels: alias, def, define_method, extend
Friday, March 09, 2007
Ruby: instance_eval and class_eval method definitions
In yesterday's entry on Cleaning up Controller Tests you can find the following code.
The above examples are from actual code, but the essence of the issue can be captured in a much simpler example.
Example 5 shows that using def in a class_eval defines an instance method on the receiver (Foo in our example). However, using def in an instance_eval defines an instance method on the singleton class of the receiver (the singleton class of Foo in our example).
This explains why example 3 does not work: the valid_person method was being defined on the singleton class of the controller test class and not as an instance method of the controller test class.
As I previously stated, using def in an instance_eval defines an instance method on the singleton class of the receiver. This also explains how using def in an instance_eval when the receiver is an instance of a class defines a method on that instance.
# example 1An earlier version of this code only used one call to the class_eval method.
...
test_class.class_eval do
define_method :setup do
...
end
end
test_class.class_eval &block
...
# example 2Example 2 works when your controller tests contain only test methods.
...
test_class.class_eval do
define_method :setup do
...
end
instance_eval &block
end
...
# example 3However, Example 2 stops working if your test class contains any helper methods.
controller_tests do
test "should add the params" do
assert_equal 4, Math.add(2,2)
end
test "should multiply the params" do
assert_equal 9, Math.multiply(3,3)
end
end
# example 4So, what's the difference between example 1 and example 2? The context in which the block is evaluated.
controller_tests do
test "should not save if first name is nil" do
person = valid_person
person.first_name = nil
assert_equal false, person.save
end
def valid_person
Person.new(...)
end
end
The above examples are from actual code, but the essence of the issue can be captured in a much simpler example.
# example 5As you can see in example 5, using def in an instance_eval is different from using def in a class_eval method call.
Foo = Class.new
Foo.class_eval do
def bar
"bar"
end
end
Foo.instance_eval do
def baz
"baz"
end
end
Foo.bar #=> undefined method ‘bar’ for Foo:Class
Foo.new.bar #=> "bar"
Foo.baz #=> "baz"
Foo.new.baz #=> undefined method ‘baz’ for #<Foo:0x7dce8>
Example 5 shows that using def in a class_eval defines an instance method on the receiver (Foo in our example). However, using def in an instance_eval defines an instance method on the singleton class of the receiver (the singleton class of Foo in our example).
This explains why example 3 does not work: the valid_person method was being defined on the singleton class of the controller test class and not as an instance method of the controller test class.
As I previously stated, using def in an instance_eval defines an instance method on the singleton class of the receiver. This also explains how using def in an instance_eval when the receiver is an instance of a class defines a method on that instance.
# example 6I've already explained where methods are stored in the various examples; however, the following example serves to prove my assertion.
Foo = Class.new
foo = Foo.new
foo.instance_eval do
def instance_bar
"instance_bar"
end
end
foo.instance_bar #=> "instance_bar"
# example 7How does define_method fit into this picture?
class Object
def singleton_class
class << self; self; end
end
end
Foo = Class.new
Foo.class_eval do
def instance_method_of_Foo; end
end
Foo.instance_eval do
def instance_method_of_Foos_singleton_class; end
end
Foo.instance_methods(false).inspect
#=> ["instance_method_of_Foo"]
Foo.singleton_class.instance_methods(false).inspect
#=> ["instance_method_of_Foos_singleton_class", "new", "superclass", "allocate"]
foo = Foo.new
foo.instance_eval do
def instance_method_of_foos_singleton_class; end
end
foo.singleton_class.instance_methods(false).inspect
#=> ["instance_method_of_Foo", "instance_method_of_foos_singleton_class"]
# example 8As you can see from example 8 define_method always defines an instance method on the target (Foo in our example). This makes sense because define_method is being call on the implicit self, which evaluates to the receiver (Foo) in our example.
Foo = Class.new
Foo.class_eval do
define_method :instance_method_of_Foo do end
end
Foo.instance_eval do
define_method :another_instance_method_of_Foo do end
end
Foo.instance_methods(false).inspect
#=> ["another_instance_method_of_Foo", "instance_method_of_Foo"]
# example 9By combining examples 5 and 8 I can create the following code that should no longer be surprising.
Foo.class_eval do
self #=> Foo
end
Foo.instance_eval do
self #=> Foo
end
class ObjectThanks to Ali Aghareza, Pat Farley, and Zak Tamsen for collaborating with me on this.
def singleton_class
class << self; self; end
end
end
Foo = Class.new
Foo.instance_eval do
def an_instance_method_of_the_singleton_class
end
define_method :an_instance_method do
end
end
Foo.instance_methods(false).inspect
#=> ["an_instance_method"]
Foo.singleton_class.instance_methods(false).inspect
#=> ["an_instance_method_of_the_singleton_class", "new", "superclass", "allocate"]
Labels: class_eval, def, define_method, instance_eval, metaprogramming, ruby


