2008-11-14 7 views
46

¿Hay algo de lo que tenga cuidado al definir el método method_missing en Ruby? Me pregunto si hay algunas interacciones no tan obvias de herencia, lanzamiento de excepción, rendimiento o cualquier otra cosa.method_missing gotchas en Ruby

Respuesta

57

Algo obvio: siempre redefina respond_to? si redefine method_missing. Si method_missing(:sym) funciona, respond_to?(:sym) siempre debe devolver verdadero. Hay muchas bibliotecas que confían en esto.

después:

Un ejemplo:

# 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, include_private = false) 
    pass_sym_to_foo?(sym) || super(sym, include_private) 
    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 
+0

Eso es interesante.¿Cómo implementaría eso para una clase que consta de métodos "normales" y métodos "dinámicos" (implementados a través de method_missing)? –

+0

@Christoph: su método 'pass_sym_to_foo?' Se convierte en un método genérico 'handle?' Que decide si trata de procesar esta solicitud o la transfiere a 'method_missing' de' super'. –

+16

En Ruby 1.9.2, es aún mejor redefinir 'responder_a_missing?', Ver la publicación de mi blog: http://blog.marc-andre.ca/2010/11/methodmissing-politely.html –

9

Si puede anticipar los nombres de método, es mejor declarar dinámicamente que confiar en method_missing porque method_missing incurre en una penalización de rendimiento. Por ejemplo, suponga que desea extender un mango de base de datos para poder acceder a las vistas de base de datos con la siguiente sintaxis:

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

lugar de confiar en method_missing en el mango de base de datos y el envío del nombre del método a la base de datos como el nombre de una vista, puede determinar todas las vistas en la base de datos con anticipación, luego iterar sobre ellas para crear métodos "viewname" en @dbh.

5

Construyendo en Pistos's point: method_missing es al menos un orden de magnitud más lento que el método habitual de invocación de todas las implementaciones de Ruby que he probado. Tiene razón al anticipar cuando sea posible para evitar llamadas al method_missing.

Si te sientes aventurero, echa un vistazo a la poco conocida clase Delegator de Ruby.

11

Si el método que falta de método solo busca determinados nombres de método, no olvide llamar a super si no ha encontrado lo que está buscando, para que otras pérdidas de método puedan hacer su trabajo.

+1

Sí, de lo contrario, la llamada a su método fallará en silencio y pasará horas tratando de descubrir por qué su método no funciona aunque no haya errores. (No es que hubiera hecho tal cosa) – PhillipKregg

0

Otra Gotcha:

method_missing comporta de manera diferente entre obj.call_method y obj.send(:call_method). Esencialmente, el primero omite todos los métodos privados y no definidos, mientras que uno más tarde no se pierde los métodos privados.

Así que method_missing nunca atrapará la llamada cuando alguien llame a su método privado a través del send.

0

respuesta de James es grande, pero, en rubí moderna (1.9+), al igual que Marc-André está diciendo, desea volver a definir respond_to_missing?, ya que le da acceso a otros métodos en la parte superior de respond_to?, como method(:method_name) devolver el método mismo .

ejemplo, la clase siguiente definido:

class UserWrapper 
    def initialize 
    @json_user = { first_name: 'Jean', last_name: 'Dupont' } 
    end 

    def method_missing(sym, *args, &block) 
    return @json_user[sym] if @json_user.keys.include?(sym) 
    super 
    end 

    def respond_to_missing?(sym, include_private = false) 
    @json_user.keys.include?(sym) || super 
    end 
end 

Resultados en:

irb(main):015:0> u = UserWrapper.new 
=> #<UserWrapper:0x00007fac7b0d3c28 @json_user={:first_name=>"Jean", :last_name=>"Dupont"}> 
irb(main):016:0> u.first_name 
=> "Jean" 
irb(main):017:0> u.respond_to?(:first_name) 
=> true 
irb(main):018:0> u.method(:first_name) 
=> #<Method: UserWrapper#first_name> 
irb(main):019:0> u.foo 
NoMethodError (undefined method `foo' for #<UserWrapper:0x00007fac7b0d3c28>) 

lo tanto, siempre definir respond_to_missing? cuando anulando method_missing.

Cuestiones relacionadas