2008-11-14 3 views
46

Есть ли какие-нибудь вопросы, которые следует учитывать при определении метода method_missing в Ruby? Мне интересно, есть ли какие-то не столь очевидные взаимодействия от наследования, выброса исключений, производительности или чего-то еще.method_missing gotchas in Ruby

ответ

57

Несколько очевидный: всегда переопределяйте respond_to? если вы переопределяете method_missing. Если method_missing(:sym) работает, respond_to?(:sym) должен всегда возвращать true. На это много разных библиотек.

Позже:

Пример:

# 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

Это интересно.Как вы реализуете это для класса, который состоит из «нормальных» методов и «динамических» методов (реализуемых методом method_missing)? – 2008-11-15 00:00:08

+0

@Christoph: Ваш метод `pass_sym_to_foo?` Становится универсальным методом `handle? ', Который решает, пытаться ли обработать этот запрос или передать его` `method`missing`` super`. – 2010-06-18 15:39:14

9

Если вы можете предвидеть имена методов, то лучше динамически объявлять их, чем полагаться на method_missing, поскольку method_missing влечет за собой снижение производительности. Например, предположим, что вы хотите расширить ручку базы данных, чтобы иметь возможность получить доступ представления базы данных с этим синтаксисом:

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

Вместо того, чтобы полагаться на method_missing на ручке базы данных и диспетчерских имя метода в базу данных в качестве имени в представлении вы могли бы заранее определить все представления в базе данных, а затем перебрать их для создания методов «viewname» на @dbh.

5

Building on Pistos's point: method_missing по крайней мере на порядок медленнее обычного метода, вызывающего все реализации Ruby, которые я пробовал. Он прав, когда можно избежать звонков до method_missing.

Если вы чувствуете себя авантюристом, ознакомьтесь с малоизвестным классом Delegator Ruby.

11

Если ваш метод отсутствует, он ищет только имена методов, не забудьте вызвать супер, если вы не нашли то, что ищете, чтобы другие пропущенные методы могли выполнять свою задачу.

0

Другой Гоча:

method_missing ведет себя по-разному между obj.call_method и obj.send(:call_method). По сути, первый из них пропускает все частные и неопределенные методы, а позже не пропускает частные методы.

Таким образом, вы, method_missing, никогда не поймаете вызов, когда кто-то вызывает ваш частный метод через send.

0

ответ Джеймс велик, но в современном рубине (1.9+), как Марк-Андре говорит, вы хотите переопределить respond_to_missing?, потому что это дает вам доступ к другим методам поверх respond_to?, как method(:method_name) возвращения самого метода ,

Например, определяется следующий класс:

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 

Результаты в:

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>) 

Таким образом, всегда определяют respond_to_missing? при переопределении method_missing.