Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

instance_eval vs class_eval in module

Tags:

ruby

class Foo
    include Module.new { class_eval "def lab; puts 'm' end" }

    def lab
      super 
      puts 'c'
    end
end

Foo.new.lab #=> m c

========================================================================

class Foo
    include Module.new { instance_eval "def lab; puts 'm' end" }

    def lab
      super 
      puts 'c'
    end
end

Notice here I changed class_eval to instance_eval

Foo.new.lab rescue nil#=> no super class method lab
Foo.lab #=> undefined method lab for Foo class

So it seems that including the module neither defined an instance method nor a class method.

Any explanation what's going on here?

This code was tested on ruby 1.8.7 on mac.

like image 589
Nick Vanderbilt Avatar asked Aug 19 '10 15:08

Nick Vanderbilt


2 Answers

First, think of what include does. it makes the instance methods of the module being included into instance methods on the including class. i.e. apart from the fact that your working example uses an anonymous module it is equivalent to:

module M1
  def lab
    puts 'm'
  end
end

class Foo
    include M1

    def lab
      super 
      puts 'c'
    end
end

Next, think of what class_eval does. It evaluates the given code in the context of the class or module. i.e. it's exactly like you reopened the module and typed the code passed to class_eval. So MyModule = Module.new { class_eval "def lab; puts 'm' end" } is equivalent to

module MyModule
  def lab
    puts 'm'
  end
end

Hopefully this explains the case that works.

When you use instance_eval you are evaluating the code within the context of the receiving object (in this case the instance of module) so MyMod2 = Module.new { instance_eval "def lab; puts 'm' end" } is equivalent to

module MyMod2
  def MyMod2.lab
    puts 'm'
  end
end

i.e. it creates a module method which you'd call via MyMod2.lab and such methods are not added as instance methods by include.


Please note: this answer borrows a bit of its explanation from an answer I wrote to a previous question asking about instance_eval vs. class_eval relating to an example from The Ruby Programming Language book. You might find that answer helpful too.

like image 125
mikej Avatar answered Nov 04 '22 02:11

mikej


including a module just takes the instance methods - you are looking for extend. luckily to get the best of both worlds, you can simply do:

module Something
  def self.included(base)
    base.extend ClassMethods
  end

  module ClassMethods
    def blah
      puts "lol"
    end
  end
end

class Test
  include Something
end 

irb:

>> Test.blah
lol
=> nil
like image 23
Michael Baldry Avatar answered Nov 04 '22 04:11

Michael Baldry