web-dev-qa-db-fra.com

Ruby send vs __send__

Je comprends le concept de some_instance.send mais j'essaie de comprendre pourquoi vous pouvez appeler cela dans les deux sens. Le Ruby Koans implique qu'il y a une raison au-delà de fournir de nombreuses façons différentes de faire la même chose. Voici les deux exemples d'utilisation:

class Foo
  def bar?
    true
  end
end

foo = Foo.new
foo.send(:bar?)
foo.__send__(:bar?)

Quelqu'un a une idée à ce sujet?

145
jaydel

Certaines classes (par exemple la classe socket de la bibliothèque standard) définissent leur propre méthode send qui n'a rien à voir avec Object#send. Donc, si vous voulez travailler avec des objets de n'importe quelle classe, vous devez utiliser __send__ être en sécurité.

Maintenant cela laisse la question, pourquoi il y a send et pas seulement __send__. S'il n'y avait que __send__ le nom send pourrait être utilisé par d'autres classes sans aucune confusion. La raison en est que send a existé en premier et seulement plus tard, il a été réalisé que le nom send pouvait également être utilisé utilement dans d'autres contextes, donc __send__ a été ajouté (c'est la même chose qui s'est produite avec id et object_id au fait).

231
sepp2k

Si vous vraiment avez besoin de send pour vous comporter comme il le ferait normalement, vous devez utiliser __send__, car il ne sera pas (il ne devrait pas) être annulé. En utilisant __send__ est particulièrement utile dans la métaprogrammation, lorsque vous ne savez pas quelles méthodes la classe manipulée définit. Il aurait pu remplacer send.

Regarder:

class Foo
  def bar?
    true
  end

  def send(*args)
    false
  end
end

foo = Foo.new
foo.send(:bar?)
# => false
foo.__send__(:bar?)
# => true

Si vous remplacez __send__, Ruby émettra un avertissement:

avertissement: la redéfinition de "__send__" peut entraîner de graves problèmes

Certains cas où il serait utile de remplacer send seraient où ce nom est approprié, comme le passage de messages, les classes de socket, etc.

31
Thiago Silveira

__send__ Existe donc il ne peut pas être écrasé par accident.

Quant à savoir pourquoi send existe: je ne peux parler pour personne d'autre, mais object.send(:method_name, *parameters) est plus joli que object.__send__(:method_name, *parameters), donc j'utilise send sauf si je besoin pour utiliser __send__.

9
Andrew Grimm

Mis à part ce que les autres vous ont déjà dit et ce qui revient à dire que send et __send__ sont deux alias de la même méthode, vous pourriez être intéressé par la troisième possibilité, quelque peu différente, qui est public_send. Exemple:

A, B, C = Module.new, Module.new, Module.new
B.include A #=> error -- private method
B.send :include, A #=> bypasses the method's privacy
C.public_send :include, A #=> does not bypass privacy

Mise à jour: depuis Ruby 2.1, Module#include et Module#extend les méthodes deviennent publiques, donc l'exemple ci-dessus ne fonctionnerait plus.

6
Boris Stitnicky

La principale différence entre envoyer, __send__, et public_send est comme suit.

  1. envoyer et __send__ sont techniquement les mêmes que celles utilisées pour appeler la méthode Object, mais la principale différence est que vous pouvez remplacer la méthode d'envoi sans aucun avertissement et lorsque vous remplacez __send__ puis il y a un message d'avertissement

avertissement: redéfinir __send__ peut causer de graves problèmes

En effet, pour éviter les conflits, en particulier dans les gemmes ou les bibliothèques lorsque le contexte où il sera utilisé est inconnu, utilisez toujours __send__ au lieu d'envoyer.

  1. La différence entre envoyer (ou __send__) et public_send est que send/__send__ peut appeler les méthodes privées d'un objet, et public_send ne peut pas.
class Foo
   def __send__(*args, &block)
       "__send__"
   end
   def send(*args)
     "send"
   end
   def bar
       "bar"
   end
   private
   def private_bar
     "private_bar"
   end
end

Foo.new.bar #=> "bar"
Foo.new.private_bar #=> NoMethodError(private method 'private_bar' called for #Foo)

Foo.new.send(:bar) #=> "send"
Foo.new.__send__(:bar) #=> "__send__"
Foo.new.public_send(:bar) #=> "bar"

Foo.new.send(:private_bar) #=> "send"
Foo.new.__send__(:private_bar) #=> "__send__"
Foo.new.public_send(:private_bar) #=> NoMethodError(private method 'private_bar' called for #Foo)

À la fin, essayez d'utiliser public_send pour éviter l'appel direct à la méthode privée au lieu d'utiliser __send__ ou send.

0
Kishor Vyavahare