Ruby send vs __send__

151

Entendo o conceito de some_instance.sendmas estou tentando descobrir por que você pode chamar isso de duas maneiras. Os Ruby Koans sugerem que há alguma razão além de fornecer várias maneiras diferentes de fazer a mesma coisa. Aqui estão os dois exemplos de uso:

class Foo
  def bar?
    true
  end
end

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

Alguém tem alguma idéia sobre isso?

jaydel
fonte

Respostas:

242

Algumas classes (por exemplo, a classe de soquete da biblioteca padrão) definem seu próprio sendmétodo que não tem nada a ver Object#send. Portanto, se você deseja trabalhar com objetos de qualquer classe, é necessário __send__estar do lado seguro.

Agora isso deixa a questão, por que existe sende não apenas __send__. Se houvesse apenas __send__o nome, sendpoderia ser usado por outras classes sem nenhuma confusão. A razão para isso é que sendexistia primeiro e somente mais tarde foi percebido que o nome sendtambém poderia ser útil em outros contextos, portanto__send__ foi adicionado (que é a mesma coisa que aconteceu com ide object_idpela maneira).

sepp2k
fonte
8
Além disso, o BasicObject (introduzido no Ruby 1.9) apenas possui __send__, não send.
Andrew Marshall
Boa resposta. Pode ser ainda melhor se mencionado public_send, o que geralmente é preferível a sendqualquer forma.
Marc-André Lafortune
31

Se você realmente precisa sendse comportar como faria normalmente, deve usá- __send__lo, porque não será (não deveria) ser vencido. O uso __send__é especialmente útil na metaprogramação, quando você não sabe quais métodos a classe que está sendo manipulada define. Poderia ter vencido send.

Ver:

class Foo
  def bar?
    true
  end

  def send(*args)
    false
  end
end

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

Se você substituir __send__, Ruby emitirá um aviso:

aviso: redefinir `__send__ 'pode causar problemas sérios

Alguns casos em que seria útil substituir sendseriam onde esse nome é apropriado, como passagem de mensagens, classes de soquete etc.

Thiago Silveira
fonte
9

__send__ existe para que não possa ser sobrescrito por acidente.

Quanto à razão sendexiste: Eu não posso falar por ninguém, mas object.send(:method_name, *parameters)parece mais agradável do que object.__send__(:method_name, *parameters), então eu uso senda menos que eu preciso usar __send__.

Andrew Grimm
fonte
6

Além do que os outros já lhe disseram, e o que se resume a dizer isso sende __send__são dois pseudônimos do mesmo método, você pode estar interessado na terceira possibilidade, que é diferente public_send. Exemplo:

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

Atualização: Desde o Ruby 2.1, Module#includee os Module#extendmétodos se tornam públicos, o exemplo acima não funcionaria mais.

Boris Stitnicky
fonte
0

A principal diferença entre send __send__, e public_send é a seguinte.

  1. send e __send__são tecnicamente iguais aos usados ​​para chamar o método Object, mas a principal diferença é que você pode substituir o método send sem nenhum aviso e quando você o substitui __send__, há uma mensagem de aviso

aviso: redefinir __send__pode causar problemas sérios

Isso ocorre para evitar conflitos, especialmente em gemas ou bibliotecas, quando o contexto em que será usado é desconhecido, sempre use em __send__vez de enviar.

  1. A diferença entre send (ou __send__) e public_send é que enviar / __send__pode chamar os métodos privados de um objeto, e public_send não pode.
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)

No final, tente usar public_send para evitar a chamada direta ao método privado em vez de usar __send__ ou send.

Kishor Vyavahare
fonte