Dynamically defining a method on a single instance in Ruby
Given the following initial code…
class Dog
def initialize(name)
@name = name
end
def bark
puts "woof"
end
end
charlie = Dog.new("Charlie")
lucas = Dog.new("Lucas")
… one might want to teach one of the dogs a new trick, while the others shouldn’t automatically learn it. One possible solution could look like the following:
def charlie.jump
puts "wee, #@name is jumping around"
end
Another way would be to provide a teach_trick method, which dynamically defines a new method:
class Dog
# [...]
def teach_trick(name, &block)
(class << self; self; end).class_eval do
define_method name, &block
end
end
end
charlie = Dog.new("Charlie")
lucas = Dog.new("Lucas")
charlie.teach_trick :dance do
puts "#@name is dancing around!"
end
charlie.dance # >> Charlie is dancing around!
lucas.dance # ~> -:23:in `<main>': undefined method `dance' for #<Dog:0x9a62e70 @name="Lucas"> (NoMethodError)
What this does is it defines a new method on the eigenclass of the instance of a dog, so when the called method, dance
in this case, is not found in the instance methods of the class Dog, it searches in the eigenclass of the instance
instead.
One advantage of this solution is that the actual implementation of how new tricks are taught is hidden, the user does not need to know he is defining a method anymore, he is just teaching his dog a new trick. Another example is when you have to define a bunch of methods depending on some input, like a hash, which is completly dynamic.
One disadvantage, on the other hand, is that calling methods defined by define_method is a bit slower than those defined by def, but the factor is so small that you probably won’t notice it in real applications:
Benchmark.bmbm(10) do |x|
x.report("eigenclass:") { 1_000_000.times {d1.trick} }
x.report("instance def:") { 1_000_000.times {d2.trick} }
end
# >> Rehearsal -------------------------------------------------
# >> eigenclass: 0.440000 0.000000 0.440000 ( 0.453996)
# >> instance def: 0.320000 0.000000 0.320000 ( 0.322817)
# >> ---------------------------------------- total: 0.760000sec
# >>
# >> user system total real
# >> eigenclass: 0.450000 0.000000 0.450000 ( 0.451315)
# >> instance def: 0.310000 0.000000 0.310000 ( 0.308632)