tags:

views:

1046

answers:

3

Are there any things to be careful about when defining the method_missing method in Ruby? I'm wondering whether there are some not-so-obvious interactions from inheritance, exception throwing, performance, or anything else.

+11  A: 

A somewhat obvious one: always redefine respond_to? if you redefine method_missing. If method_missing(:sym) works, respond_to?(:sym) should always return true. There are many libraries that rely on this.

Later:

An example:

# Wrap a Foo; don't expose the internal guts.
# Pass any method that starts with 'a' on to the
# Foo.
class FooWrapper
  def initialize(foo)
    @foo = foo
  end
  def some_method_that_doesnt_start_with_a
    'bar'
  end
  def a_method_that_does_start_with_a
    'baz'
  end
  def respond_to?(sym)
    pass_sym_to_foo?(sym) || super(sym)
  end
  def method_missing(sym, *args, &block)
    return foo.call(sym, *args, &block) if pass_sym_to_foo?(sym)
    super(sym, *args, &block)
  end
  private
  def pass_sym_to_foo?(sym)
    sym.to_s =~ /^a/ && @foo.respond_to?(sym)
  end
end

class Foo
  def argh
    'argh'
  end
  def blech
    'blech'
  end
end

w = FooWrapper.new(Foo.new)

w.respond_to?(:some_method_that_doesnt_start_with_a)
# => true
w.some_method_that_doesnt_start_with_a
# => 'bar'

w.respond_to?(:a_method_that_does_start_with_a)
# => true
w.a_method_that_does_start_with_a
# => 'baz'

w.respond_to?(:argh)
# => true
w.argh
# => 'argh'

w.respond_to?(:blech)
# => false
w.blech
# NoMethodError

w.respond_to?(:glem!)
# => false
w.glem!
# NoMethodError

w.respond_to?(:apples?)
w.apples?
# NoMethodError
James A. Rosen
That's interesting. How would you implement that for a class which consists of "normal" methods and "dynamic" methods (implemented via method_missing)?
Christoph Schiessl
@Christoph: Your `pass_sym_to_foo?` method becomes a generic `handle?` method which decides whether to try to process this request or hand it off to `super`'s `method_missing`.
John Feminella
+2  A: 

If you can anticipate method names, it is better to dynamically declare them than to rely on method_missing because method_missing incurs a performance penalty. For example, suppose you wanted to extend a database handle to be able to access database views with this syntax:

selected_view_rows = @dbh.viewname( :column => value, ... )

Rather than relying on method_missing on the database handle and dispatching the method name to the database as the name of a view, you could determine all the views in the database ahead of time, then iterate over them to create "viewname" methods on @dbh.

Pistos
+1  A: 

Building on Pistos's point: method_missing is at least an order of magnitude slower than regular method calling on all the Ruby implementations I've tried. He is right to anticipate when possible to avoid calls to method_missing.

If you're feeling adventurous, check out Ruby's little-known Delegator class.

James A. Rosen