method_missing
is still considered scary, but here’s something more scary: forgetting to override respond_to?
.
Whoa, right? Just gave you an anxiety attack there.
How about #method
, though? Does that still work?
Go ahead, try it. Here, I’ll try it with you:
require 'ostruct'
class Order
def user
@_user ||= OpenStruct.new(name: 'Mike', age: 28, occupation: 'slacker')
end
def method_missing(method_name, *arguments, &block)
if method_name.to_s =~ /user_(.*)/
user.send($1, *arguments, &block)
else
super
end
end
def respond_to?(method_name, include_private = false)
method_name.to_s.start_with?('user_') || super
end
end
OK now I’ll load that into irb and play with it:
ruby-1.9.2-p290> order = Order.new
=> #<Order:0x00000001e40948>
ruby-1.9.2-p290> order.user_name
=> "Mike"
ruby-1.9.2-p290> order.respond_to?(:user_name)
=> true
ruby-1.9.2-p290> order.method(:user_name)
NameError: undefined method `user_name' for class `Order'
from (irb):23:in `method'
from (irb):23
from /home/mike/.rvm/rubies/ruby-1.9.2-p290/bin/irb:16:in `<main>'
There is a solution! How would you like to solve your #method
problem while also using a method with a better name? For the low price of Ruby 1.9 you, too, can have this:
require 'ostruct'
class Order
def user
@_user ||= OpenStruct.new(name: 'Mike', age: 28, occupation: 'slacker')
end
def method_missing(method_name, *arguments, &block)
if method_name.to_s =~ /user_(.*)/
user.send($1, *arguments, &block)
else
super
end
end
def respond_to_missing?(method_name, include_private = false)
method_name.to_s.start_with?('user_') || super
end
end
But you don’t have to take my word for it:
ruby-1.9.2-p290> order = Order.new
=> #<Order:0x00000001c8d678>
ruby-1.9.2-p290> order.user_name
=> "Mike"
ruby-1.9.2-p290> order.respond_to?(:user_name)
=> true
ruby-1.9.2-p290> order.method(:user_name)
=> #<Method: Order#user_name>
If I were to tweet about this, this is what I would say: always define respond_to_missing?
when overriding method_missing
.