tokhi
tokhi

Reputation: 21618

When to use `method_missing`

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

Answers (5)

doesterr
doesterr

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

akuhn
akuhn

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

tokhi
tokhi

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

Sergio Tulentsev
Sergio Tulentsev

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

tadman
tadman

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

Related Questions