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.
-
A somewhat obvious one: always redefine
respond_to?if you redefinemethod_missing. Ifmethod_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? # NoMethodErrorChristoph Schiessl : That's interesting. How would you implement that for a class which consists of "normal" methods and "dynamic" methods (implemented via method_missing)?John Feminella : @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`. -
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.
-
Building on Pistos's point:
method_missingis 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 tomethod_missing.If you're feeling adventurous, check out Ruby's little-known Delegator class.
0 comments:
Post a Comment