Есть ли какие-нибудь вопросы, которые следует учитывать при определении метода method_missing
в Ruby? Мне интересно, есть ли какие-то не столь очевидные взаимодействия от наследования, выброса исключений, производительности или чего-то еще.method_missing gotchas in Ruby
ответ
Несколько очевидный: всегда переопределяйте 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
Если вы можете предвидеть имена методов, то лучше динамически объявлять их, чем полагаться на method_missing, поскольку method_missing влечет за собой снижение производительности. Например, предположим, что вы хотите расширить ручку базы данных, чтобы иметь возможность получить доступ представления базы данных с этим синтаксисом:
selected_view_rows = @dbh.viewname(:column => value, ...)
Вместо того, чтобы полагаться на method_missing на ручке базы данных и диспетчерских имя метода в базу данных в качестве имени в представлении вы могли бы заранее определить все представления в базе данных, а затем перебрать их для создания методов «viewname» на @dbh.
Building on Pistos's point: method_missing
по крайней мере на порядок медленнее обычного метода, вызывающего все реализации Ruby, которые я пробовал. Он прав, когда можно избежать звонков до method_missing
.
Если вы чувствуете себя авантюристом, ознакомьтесь с малоизвестным классом Delegator Ruby.
Если ваш метод отсутствует, он ищет только имена методов, не забудьте вызвать супер, если вы не нашли то, что ищете, чтобы другие пропущенные методы могли выполнять свою задачу.
Другой Гоча:
method_missing
ведет себя по-разному между obj.call_method
и obj.send(:call_method)
. По сути, первый из них пропускает все частные и неопределенные методы, а позже не пропускает частные методы.
Таким образом, вы, method_missing
, никогда не поймаете вызов, когда кто-то вызывает ваш частный метод через send
.
ответ Джеймс велик, но в современном рубине (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
.
Это интересно.Как вы реализуете это для класса, который состоит из «нормальных» методов и «динамических» методов (реализуемых методом method_missing)? – 2008-11-15 00:00:08
@Christoph: Ваш метод `pass_sym_to_foo?` Становится универсальным методом `handle? ', Который решает, пытаться ли обработать этот запрос или передать его` `method`missing`` super`. – 2010-06-18 15:39:14