Created
August 24, 2013 02:53
-
-
Save richardking/6325780 to your computer and use it in GitHub Desktop.
instance_eval, class_eval
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Ruby: instance_eval and class_eval method definitions | |
| In yesterday's entry on Cleaning up Controller Tests you can find the following code. | |
| # example 1 | |
| ... | |
| test_class.class_eval do | |
| define_method :setup do | |
| ... | |
| end | |
| end | |
| test_class.class_eval &block | |
| ... | |
| An earlier version of this code only used one call to the class_eval method. | |
| # example 2 | |
| ... | |
| test_class.class_eval do | |
| define_method :setup do | |
| ... | |
| end | |
| instance_eval &block | |
| end | |
| ... | |
| Example 2 works when your controller tests contain only test methods. | |
| # example 3 | |
| 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 | |
| However, Example 2 stops working if your test class contains any helper methods. | |
| # example 4 | |
| 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 | |
| So, what's the difference between example 1 and example 2? The context in which the block is evaluated. | |
| The above examples are from actual code, but the essence of the issue can be captured in a much simpler example. | |
| # example 5 | |
| 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> | |
| As you can see in example 5, using def in an instance_eval is different from using def in a class_eval method call. | |
| 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 6 | |
| Foo = Class.new | |
| foo = Foo.new | |
| foo.instance_eval do | |
| def instance_bar | |
| "instance_bar" | |
| end | |
| end | |
| foo.instance_bar #=> "instance_bar" | |
| I've already explained where methods are stored in the various examples; however, the following example serves to prove my assertion. | |
| # example 7 | |
| 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"] | |
| How does define_method fit into this picture? | |
| # example 8 | |
| 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"] | |
| As 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. | |
| # example 9 | |
| Foo.class_eval do | |
| self #=> Foo | |
| end | |
| Foo.instance_eval do | |
| self #=> Foo | |
| end | |
| By combining examples 5 and 8 I can create the following code that should no longer be surprising. | |
| class Object | |
| 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"] | |
| http://blog.jayfields.com/2007/03/ruby-instanceeval-and-classeval-method.html |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment