Y a-t-il des éléments à prendre en compte lors de la définition de la méthode method_missing
dans Ruby? Je me demande s'il existe des interactions moins évidentes entre héritage, lancement d'exceptions, performances ou autre chose.
Un exemple assez évident: redéfinissez toujours respond_to?
si vous redéfinissez method_missing
. Si method_missing(:sym)
fonctionne, respond_to?(:sym)
doit toujours renvoyer true. Il y a beaucoup de bibliothèques qui en dépendent.
Plus tard:
Un exemple:
# 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
Si votre méthode manquante est uniquement à la recherche de certains noms de méthodes, n'oubliez pas d'appeler super si vous n'avez pas trouvé ce que vous cherchez, pour que les autres méthodes manquantes puissent faire l'affaire.
Si vous pouvez anticiper les noms de méthodes, il est préférable de les déclarer dynamiquement que de s'appuyer sur method_missing, car method_missing entraîne une pénalité de performance. Par exemple, supposons que vous vouliez étendre un handle de base de données pour pouvoir accéder aux vues de la base de données avec la syntaxe suivante:
selected_view_rows = @dbh.viewname( :column => value, ... )
Plutôt que de s'appuyer sur method_missing sur le handle de base de données et d'envoyer le nom de la méthode à la base de données en tant que nom d'une vue, vous pouvez déterminer toutes les vues de la base de données à l'avance, puis les parcourir pour créer des méthodes "viewname" sur @dbh. .
Construire sur Le point de Pistos : method_missing
est au moins un ordre de grandeur plus lent que la méthode habituelle appelant toutes les implémentations de Ruby que j'ai essayées. Il a raison d’anticiper dans la mesure du possible d’éviter les appels au method_missing
.
Si vous vous sentez aventureux, consultez le peu connu Delegator class de Ruby.
La réponse de James est excellente, mais dans Ruby moderne (1.9+), comme le dit Marc-André, vous souhaitez redéfinir respond_to_missing?
car il vous donne accès à d'autres méthodes au-dessus de respond_to?
, comme method(:method_name)
, ce qui permet de restituer la méthode elle-même.
Exemple, la classe suivante définie:
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
Résulte 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>)
Donc, définissez toujours respond_to_missing?
lors de la substitution de method_missing
.
Un autre gotcha:
method_missing
se comporte différemment entre obj.call_method
et obj.send(:call_method)
. Essentiellement, le premier manque toutes les méthodes privées et non définies, tandis que plus tard, il ne manque pas de méthodes privées.
Donc, vous method_missing
ne serez jamais dérouté lorsque quelqu'un appelle votre méthode privée via send
.