Reputation: 21618
In the code below, method roar
is not defined in class Lion
, but still can be called using method_missing
.
class Lion
def method_missing(name, *args)
puts "Lion will #{name}: #{args[0]}"
end
end
lion = Lion.new
lion.roar("ROAR!!!") # => Lion will roar: ROAR!!!
In which situations and how should I use this method_missing
? And is it safe to use?
Upvotes: 8
Views: 6342
Reputation: 3965
First and foremost, stick to Sergio Tulentsev's summary.
Apart from that, I think looking at examples is the best way to get a feeling for right and wrong situations for method_missing
; so here is another simple example:
I recently made use of method_missing
in a Null Object.
The Null Object was a replacement for a Order model.
The Order stores different prices for different currencies.
Without method_missing
it looks like this:
class NullOrder
def price_euro
0.0
end
def price_usd
0.0
end
# ...
# repeat for all other currencies
end
With method_missing
, I can shorten it to:
class NullOrder
def method_missing(m, *args, &block)
m.to_s =~ /price_/ ? 0.0 : super
end
end
With the added benefit of not having to (remember to) update the NullOrder
when I add new price_xxx
attributes to Order
.
Upvotes: 4
Reputation: 27793
Here's a favorite of mine
class Hash
def method_missing(sym,*args)
fetch(sym){fetch(sym.to_s){super}}
end
end
Which lets me access values of a hash as if they were attributes. This is particular handy when working with JSON data.
So for example, rather than having to write tweets.collect{|each|each['text']}
I can just write tweets.collect(&:text)
which is much shorter. Or also, rather than tweets.first['author']
I can just write tweets.first.author
which feels much more natural. Actually, it gives you Javascript-style access to values of a hash.
NB, I'm expecting the monkey patching police at my door any minute…
Upvotes: 4
Reputation: 21618
I also found a blog post from (Paolo Perrotta) where it demonstrated when to use method_missing:
class InformationDesk
def emergency
# Call emergency...
"emergency() called"
end
def flights
# Provide flight information...
"flights() called"
end
# ...even more methods
end
Check if a service has been asked during lunch time.
class DoNotDisturb
def initialize
@desk = InformationDesk.new
end
def method_missing(name, *args)
unless name.to_s == "emergency"
hour = Time.now.hour
raise "Out for lunch" if hour >= 12 && hour < 14
end
@desk.send(name, *args)
end
end
# At 12:30...
DoNotDisturb.new.emergency # => "emergency() called"
DoNotDisturb.new.flights # ~> -:37:in `method_missing': Out for lunch (RuntimeError)
Upvotes: 3
Reputation: 230276
Summary: When to use? When it will make your life easier and not complicate others' lives.
Here's one example that pops to mind. It's from redis_failover gem.
# Dispatches redis operations to master/slaves.
def method_missing(method, *args, &block)
if redis_operation?(method)
dispatch(method, *args, &block)
else
super
end
end
Here we check if the method called is actually a command of redis connection. If so, we delegate it to underlying connection(s). If not, relay to super.
Another famous example of method_missing
application is ActiveRecord finders.
User.find_by_email_and_age('[email protected]', 20)
There's not, of course, a method find_by_email_and_age
. Instead, the method_missing
breaks the name, analyzes the parts and invokes find
with proper parameters.
Upvotes: 8
Reputation: 211540
It's entirely safe to use provided you use it in expected ways and don't get carried away. Not everything you can do is worth doing, after all.
The advantage of method_missing
is you can respond to all kinds of things in unique ways.
The disadvantage is you don't advertise your capabilities. Other objects that expect you to respond_to?
something will not get confirmation and might treat your custom object in ways you don't intend.
For building Domain Specific Languages and providing very loose glue between components, this sort of thing is invaluable.
A great example of where this is a good fit is the Ruby OpenStruct class.
Upvotes: 9