Dynamic Methods vs. Method Missing
For a class I'm organizing on metaprograming in Ruby, we’re using Metaprograming Ruby as a textbook. In the chapter Tuesday: Methods, the book introduces two techniques for removing duplication: dynamically defining methods and using method_missing. It gives examples of applying both techniques to some sample code of which a simplified version is reproduced below:
# initial code
class Computer
def initialize(data_source)
@data_source = data_source
end
def mouse
puts "Price: #{data_source.mouse_price}"
end
def keyboard
puts "Price: #{data_source.mouse_price}"
end
def monitor
puts "Price: #{data_source.mouse_price}"
end
#...
end
# dynamic methods
class Computer
def initialize(data_source)
@data_source.methods.grep(/^(.*)_price$/) { Computer.define_component $1 }
end
def self.define_component(name)
define_method(name) do
puts "Price: #{data_source.send("#{name}_price")
end
end
end
# method_missing
class Computer
instance_methods.each do |m|
undef_method m unless m.to_s =~ /^__|method_missing|respond_to?/
end
def method_missing(name, *args)
super if !respond_to?(name)
puts "Price: #{data_source.send("#{name}_price")
end
def respond_to?(method)
@data_source.respond_to?("#{name}_price")
end
end
However, the book doesn't discuss when you should use one technique versus another. Here’s my rule: only use method_missing when it is infeasible to use dynamic methods.
One good example of using method_missing is ActiveRecord's find_by method. With it, you can call a method like find_by_name_and_birthdate. While ActiveRecord could theoretically use the dynamic method technique, generating every possible method would be overkill, thus using method_missing makes sense.
One counter example can be found in an example in the Metaprograming Ruby book itself:
class Roulette
def method_missing(name, *args)
person = name.to_s.capitalize
super unless %w[Bob Frank Bill].include? person
number = 0
3.times do
number = rand(10) + 1
puts "#{number}..."
end
"#{person} got a #{number}"
end
end
In this case, we have three methods we want Roulette to respond to: bob, frank, and bill. As we know these methods in advance, you should use the dynamic_method strategy:
class Roulette
%w[bob frank bill].each do |name|
define_method name do
number = 0
3.times do
number = rand(10) + 1
puts "#{number}..."
end
"#{name.to_s.capitalize} got a #{number}"
end
end
end
Now back to our initial example with Computer, which technique should we apply? Given that we can apply the dynamic methods technique, we should use it. Oh, and assuming we are always passed an instance of DataStore, I'd refactor it like so:
# dynamic methods
class Computer
DataSource.methods.grep(/^(.*)_price$/).each do |name|
define_method(name) do
puts "Price: #{data_source.send("#{name}_price")
end
end
end


Social Links