it-swarm.com.de

method_missing gotchas in Ruby

Gibt es etwas zu beachten, wenn die method_missing-Methode in Ruby definiert wird? Ich frage mich, ob es nicht so offensichtliche Wechselwirkungen zwischen Vererbung, Ausnahmewurf, Leistung oder etwas anderem gibt.

48
Readonly

Eine etwas offensichtliche: Definieren Sie respond_to? immer neu, wenn Sie method_missing neu definieren. Wenn method_missing(:sym) funktioniert, sollte respond_to?(:sym) immer true zurückgeben. Es gibt viele Bibliotheken, die sich darauf verlassen.

Später:

Ein Beispiel:

# 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
59
James A. Rosen

Wenn Ihre Methode fehlende Methode nur nach bestimmten Methodennamen sucht, vergessen Sie nicht, super aufzurufen, wenn Sie nicht gefunden haben, wonach Sie suchen, damit andere Methodenfehlschläge ihre Sache tun können.

13
Andrew Grimm

Wenn Sie Methodennamen vorhersehen können, ist es besser, sie dynamisch zu deklarieren, als sich auf method_missing zu verlassen, da method_missing eine Leistungsbeeinträchtigung mit sich bringt. Angenommen, Sie möchten ein Datenbankhandle erweitern, um mit dieser Syntax auf Datenbankansichten zugreifen zu können:

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

Anstatt sich darauf zu verlassen, dass method_missing das Datenbankhandle verwendet und den Methodennamen als Namen einer Ansicht an die Datenbank weiterleitet, können Sie alle Ansichten in der Datenbank im Voraus bestimmen und sie dann durchlaufen, um "viewname" -Methoden auf @dbh zu erstellen .

11
Pistos

Aufbauend auf Pistos 'Punkt : method_missing ist mindestens eine Größenordnung langsamer als die normale Methode, die alle von mir ausgeführten Ruby-Implementierungen aufruft. Er antizipiert zu Recht, wenn es möglich ist, um Anrufe bei method_missing zu vermeiden.

Wenn Sie sich abenteuerlustig fühlen, schauen Sie sich Rubys wenig bekannte Delegator Klasse an.

5
James A. Rosen

Die Antwort von James ist großartig, aber im modernen Ruby (1.9+) möchten Sie, wie Marc-André sagt, respond_to_missing? neu definieren, da Sie über respond_to? hinaus auf andere Methoden zugreifen können, z. B. auf method(:method_name)returning der Methode selbst.

Beispiel die folgende Klasse definiert:

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

Ergebnisse in:

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

Definieren Sie daher immer respond_to_missing?, wenn Sie method_missing überschreiben.

0
Capripot

Noch ein Gotcha:

method_missing verhält sich zwischen obj.call_method und obj.send(:call_method) unterschiedlich. Im Wesentlichen vermisst der erstere alle privaten und nicht definierten Methoden, während der spätere keine privaten Methoden vermisst.

Sie method_missing fangen den Aufruf also nie ab, wenn jemand Ihre private Methode über send aufruft.

0
jack2684