Basis
We will use Module#define_method. Keep in mind that this method is
private so
SomeModuleOrClass.define_method(:foo){ }
wouldn’t work. This is why the relatively obscure class <<
self; … idiom (see singleton methods)
is needed.
Defining normal instance methods
This adds a bit of dinamicity to plain reopening of the class/module plus
redefinition, cause the method is defined using a closure, and we can take
the name from a string/symbol objects without having to interpolate it into
a string passed to eval.
class A; end
a = 1
A.send(:define_method, :foo){ puts "a" }
A.send(:public, :foo)
A.new.foo
Note that we had to make the method public explicitly; however, the
following works fine:
class A
def foo; end
define_method(:foo){ puts "foo" } # redefine
define_method(:bar){ puts "bar" }
end
A.new.foo
A.new.bar
Limitations
In 1.8.x, it is still impossible to define methods that accept blocks using
define_method, so we still need cheap tricks like string interpolation +
eval.
However, in 1.9 you can do something like
module A
define_method(:foo){|*a,&b| p a; b.call }
end
extend A
foo { puts "12345" }
At that point, all you can do with eval+string interpolation is possible
with define_method, and then some more.
Defining singleton methods
define_method is especially attractive cause it works with a
closure, but class << obj opens a new scope, so a naive
attempt to define a method dynamically can be limiting:
a = 1
foo = SomeClass.new
class << foo
define_method(:bar){ a += 1 } # doesn't work, cause a is in another scope
end
In order to stay in the same scope, so that the closure captures the
environment we are interested in, we need something like
a = 1
foo = SomeClass.new
class << foo; self end.send(:define_method, :bar){ a += 1}
Let’s analyze the class << self; self
end.send(:defile_method; :x){ } idiom step by step:
| class << someobject: | opens the singleton class of someobject. Note in that in the class
body, self is the singleton class.
|
| class << someobject; self end: | returns the singleton class of someobject, that is
singleton_class = class << someobj; self end
|
| send :define_method: | define_method is a private method, so we need to call it using
Object#send which bypasses access restrictions. The argument to
define_method is given as the 2nd arg. to send. Finally,
the body of the method we want to define is passed as a block. Since the
block is specified in the "external scope" (not inside the
singleton class scope), the closure captures the environment of interest.
|
|