O que send () faz em Ruby?

94

Alguém pode me dizer o que

send("#{Model.find...}")

é e faz?

Christian Bankester
fonte
2
Esta é toda a linha de código? Quero dizer, não há nada antes de 'enviar'?
girafa

Respostas:

107

send envia uma mensagem para uma instância de objeto e seus ancestrais na hierarquia de classes até que algum método reaja (porque seu nome corresponde ao primeiro argumento).

Na prática, essas linhas são equivalentes:

1.send '+', 2
1.+(2)
1 + 2

Observe que sendignora as verificações de visibilidade, para que você também possa chamar métodos privados (útil para teste de unidade).


Se não houver realmente nenhuma variável antes de enviar, isso significa que o objeto global é usado:

send :to_s    # "main"
send :class   # Object
girafa
fonte
1
Ah, entendo, então pode-se usar send se quiser armazenar algo como 1 mês no banco de dados em vez de dizer estaticamente o número de dias.
Christian Bankester
3
É verdade que você pode usá-lo para chamar métodos com nomes que são calculados, não estáticos. (Você não deve permitir entrada irrestrita do usuário, no entanto, para evitar a chamada de métodos privados ... Você poderia, no entanto, dar a eles um prefixo exclusivo: send 'user_method _' + methodname, * args)
giraff
2
Um bom caso de uso pode ser quando você deseja testar um método de classe protegida, você pode chamá-lo fora de uma classe - no arquivo de teste ..
GN.
106

send é um método ruby ​​(sem rails) que permite invocar outro método pelo nome.

Da documentação

   class Klass
     def hello(*args)
       "Hello " + args.join(' ')
     end
   end
   k = Klass.new
   k.send :hello, "gentle", "readers"   #=> "Hello gentle readers"

http://corelib.rubyonrails.org/classes/Object.html#M001077

Nikita Rybak
fonte
5
Ótima resposta, mais clara do que a resposta verbosa aceita.
codificação aaron de
63

Um dos recursos mais úteis que eu acho com o método .send é que ele pode chamar o método dinamicamente. Isso pode economizar muito tempo de digitação. Um dos usos mais populares do método .send é atribuir atributos dinamicamente. Por exemplo:

class Car
  attr_accessor :make, :model, :year
end  

Para atribuir atributos regularmente, seria necessário

c = Car.new
c.make="Honda"
c.model="CRV"
c.year="2014"

Ou usando o método .send:

c.send("make=", "Honda")
c.send("model=", "CRV")
c.send("year=","2014")

Mas tudo pode ser substituído pelo seguinte:

Assumindo que seu aplicativo Rails precisa atribuir atributos à classe do seu carro a partir da entrada do usuário, você pode fazer

c = Car.new()
params.each do |key, value|
  c.send("#{key}=", value)
end
Antonio Jha
fonte
Obrigado pelo ótimo link
sid_09
7
Usar .send dessa maneira adiciona complexidade desnecessária e torna mais fácil introduzir inadvertidamente um bug no código. Por exemplo, no código acima, se você adicionar uma nova entrada ao hash de parâmetros (como 'cilindros'), o código falhará com um erro de método indefinido.
Kevin Schwerdtfeger
1
respond_to? pode ser usado para prevenir tais erros, se desejado.
Richard_G
1
Essa foi uma ótima explicação! Obrigado Jha!
Sharath
1
@Kevin você está certo, mas às vezes pode ser necessário. Mais flexibilidade está relacionada a mais riscos, que podem ser mitigados.
Will Sheppard
12

Outro exemplo, semelhante ao https://stackoverflow.com/a/26193804/1897857 de Antonio Jha

é se você precisa ler atributos em um objeto.

Por exemplo, se você tiver um array de strings, se tentar iterar por meio deles e chamá-los em seu objeto, não funcionará.

atts = ['name', 'description']
@project = Project.first
atts.each do |a|
  puts @project.a
end
# => NoMethodError: undefined method `a'

No entanto, você pode sendinserir as strings no objeto:

atts = ['name', 'description']
@project = Project.first
atts.each do |a|
  puts @project.send(a)
end
# => Vandalay Project
# => A very important project
Mike Vallano
fonte
Obrigado pela explicação simples e fácil!
Junan Chakma de
Obrigado! Essa é exatamente a resposta que procuro. Quer saber se isso é comumente usado? Encontrei algo semelhante no código legado, não tenho certeza se devo continuar com ele. @ Mike Vallano
B Liu
1
@ b-liu Já vi isso ser usado por desenvolvedores experientes em novos códigos. Também pode ser útil ao usar define_method: apidock.com/ruby/Module/define_method .
Mike Vallano
Impressionante! Muito obrigado! @MikeVallano
B Liu
4

O que enviar faz?

send é outra maneira de chamar um método.

Isso é melhor ilustrado por um exemplo:

o = Object.new
o.send(:to_s) # => "#<Object:0x00005614d7a24fa3>"
# is equivalent to:
o.to_s # => "#<Object:0x00005614d7a24fa3>"

Envie vidas na classe Object .

Qual é o benefício disso?

O benefício dessa abordagem é que você pode passar o método que deseja chamar como um parâmetro. Aqui está um exemplo simples:

def dynamically_call_a_method(name)
    o = Object.new
    o.send name 
end
dynamically_call_a_method(:to_s) # => "#<Object:0x00005614d7a24fa3>"

Você pode passar o método que deseja ser chamado. Neste caso, passamos :to_s. Isso pode ser muito útil ao fazer a metaprogramação do Ruby, porque nos permite chamar diferentes métodos de acordo com nossos diferentes requisitos.

BKSpurgeon
fonte
0

Outro caso de uso para visualizações:

    <%= link_to 
    send("first_part_of_path_#{some_dynamic_parameters}_end_path", 
    attr1, attr2), ....
    %>

Permitir . você deve escrever uma visão escalonável que trabalha com todos os tipos de objetos com:

    render 'your_view_path', object: "my_object"
JustAnotherRubyLover
fonte
Isso adiciona lógica desnecessária às visualizações e pode ter implicações de segurança. Não faça isso. Use matrizes e hashes.
Derrek Bertrand