Dominik Honnef

Dynamically defining a method on a single instance in Ruby

Published:
Last modified:
by

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)