Ruby send vs __send__

151

Entiendo el concepto de some_instance.sendpero estoy tratando de entender por qué puedes llamar a esto en ambos sentidos. Los Ruby Koans implican que hay alguna razón más allá de proporcionar muchas formas diferentes de hacer lo mismo. Aquí están los dos ejemplos de uso:

class Foo
  def bar?
    true
  end
end

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

Alguien tiene alguna idea sobre esto?

jaydel
fuente

Respuestas:

242

Algunas clases (por ejemplo, la clase de socket de la biblioteca estándar) definen su propio sendmétodo que no tiene nada que ver Object#send. Entonces, si desea trabajar con objetos de cualquier clase, debe usar __send__para estar seguro.

Ahora que deja la pregunta, por qué hay sendy no solo __send__. Si hubiera solo __send__el nombre sendpodría ser utilizado por otras clases sin ninguna confusión. La razón de esto es que sendexistió primero y solo más tarde se dio cuenta de que el nombre sendtambién podría usarse útilmente en otros contextos, por lo que __send__se agregó (eso es lo mismo que sucedió con idy object_idpor cierto).

sepp2k
fuente
8
Además, BasicObject (introducido en Ruby 1.9) solo tiene __send__, no send.
Andrew Marshall
Buena respuesta. Podría ser aún mejor si se menciona public_send, que a menudo es preferible a sendtodos modos.
Marc-André Lafortune
31

Si realmente necesita sendcomportarse como lo haría normalmente, debe usarlo __send__, ya que no se anulará (no debería). El uso __send__es especialmente útil en la metaprogramación, cuando no sabe qué métodos define la clase que se manipula. Podría haber anulado send.

Reloj:

class Foo
  def bar?
    true
  end

  def send(*args)
    false
  end
end

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

Si anula __send__, Ruby emitirá una advertencia:

advertencia: redefinir '__send__' puede causar serios problemas

Algunos casos en los que sería útil anular sendserían donde ese nombre es apropiado, como pasar mensajes, clases de socket, etc.

Thiago Silveira
fuente
9

__send__ existe, por lo que no se puede sobrescribir por accidente.

En cuanto a por qué sendexiste: no puedo hablar por nadie más, pero se object.send(:method_name, *parameters)ve mejor que object.__send__(:method_name, *parameters)yo, así que lo uso a sendmenos que necesite usarlo __send__.

Andrew Grimm
fuente
6

Además de lo que otros ya le dijeron, y lo que se reduce a decir eso sendy __send__son dos alias del mismo método, es posible que le interese la tercera posibilidad, que es muy diferente public_send. Ejemplo:

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

Actualización: desde Ruby 2.1, Module#includey los Module#extendmétodos se hacen públicos, por lo que el ejemplo anterior ya no funcionaría.

Boris Stitnicky
fuente
0

La principal diferencia entre send __send__, y public_send es la siguiente.

  1. enviar y __send__son técnicamente los mismos que se usan para llamar al método de Object, pero la principal diferencia es que puede anular el método de envío sin ninguna advertencia y cuando lo anula __send__, aparece un mensaje de advertencia

advertencia: redefinir __send__puede causar serios problemas

Esto se debe a que para evitar conflictos, especialmente en gemas o bibliotecas, cuando se desconoce el contexto en el que se usará, use siempre en __send__lugar de enviar.

  1. La diferencia entre send (or __send__) y public_send es que send / __send__can puede llamar a los métodos privados de un objeto y public_send no puede.
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)

Al final, intente usar public_send para evitar la llamada directa al método privado en lugar de usar __send__ o send.

Kishor Vyavahare
fuente