method_missing попал в руби - PullRequest
       71

method_missing попал в руби

48 голосов
/ 14 ноября 2008

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

Ответы [ 6 ]

59 голосов
/ 15 ноября 2008

Несколько очевидный: всегда переопределяйте 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
13 голосов
/ 23 апреля 2011

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

11 голосов
/ 15 ноября 2008

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

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

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

5 голосов
/ 18 ноября 2008

Опираясь на Точка Пистоса : method_missing по крайней мере на порядок медленнее, чем обычный метод, вызывающий все реализации Ruby, которые я пробовал. Он вправе предвидеть, когда это возможно, чтобы избежать звонков на method_missing.

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

0 голосов
/ 27 февраля 2018

Джеймс отвечает отлично, но в современном ruby ​​(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.

0 голосов
/ 13 марта 2017

Еще один гоча:

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

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

...