Add methods dynamically in ruby

原创文章,转载请注明来源并保留原文链接

A = Class.new

class << A
  def say(sth)
    puts sth
  end
end

A
.instance_eval do
  def do(action)
    puts "Do #{action}"
  end
end

A.say('I am ok') # => I am ok
A.do('go') # => Do go
A.new.do('go')
# => NoMethodError: undefined method `do' for #<A:0x007f9a232b3698>

Don’t be confused by the name instance_eval, it’s not execute block under A’s instance context.

instance_eval

Evaluates a string containing Ruby source code, or the given block, within the context of the receiver (obj).

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

Let’s take a look at class_eval

A.class_eval do
  def make(sth)
    puts "Make #{sth}"
  end
end

A.make('cookie')
# => NoMethodError: undefined method `make' for A:Class

A.new.make('cookie') # => Make cookie

It’s confused. right? class_eval named `class` but not define `class method`.

class_eval

Evaluates the string or block in the context of mod, except that when a block is given, constant/class variable lookup is not affected.

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

Let’s take a look at  metaclass

a = Object.new

def a.dance
  puts 'I am dancing'
end

a.dance # => I am dancing

We know that the method dance isn’t defined in the class Object, because if i instance a new obj, it can not `dance`

b = Object.new

b.dance
# => NoMethodError: undefined method `dance'

So where is it? The answer is metaclass. Yehuda Katz mentioned this concept in his post(metaprogramming-in-ruby-its-all-about-the-self). Each object in Ruby also has its own metaclass – a Class that can have methods, but is only attached to the object itself. metaclass is invisible in Ruby.

What’s going on here is that we’re adding the dance method to a’s metaclass, and the a object inherits from its metaclass and then Object.

Let’s get access to the metaclass.

metaclass = class << a; self; end # => #<Class:#<Object:0x007f9a23bf0620>>
metaclass.instance_methods.grep(/dance/) #=> [:dance]

End.

2 thoughts on “Add methods dynamically in ruby

  1. 既然说了 instance_eval 和 class_eval 那也把 eval 补充也说下吧
    instance_eval 是把环境绑定到某个实例的环境
    class_eval 是把环境绑定到那个类的环境
    eval 是把环境绑定到binding对象所在的环境

    class A
    def g ; binding; end
    end
    class B
    G = binding
    def self.g ; binding; end
    end
    class C
    G = class << self
    binding
    end
    end
    d = class < #
    eval “self”,B::G #=> B
    eval “self”,B.g #=> B
    eval “self”,C::G #=> #
    eval “self”,d #=> #<Class:#>

    可见 bind 能把环境绑定到任意返回binding对象的环境中

  2. 接上面的例子当然也可以直接在绑定的环境动态定义方法
    eval “define_method(:test) do; 123; end “, B::G
    B.new.test #=> 123

Leave a Reply

Your email address will not be published. Required fields are marked *