Co send () robi w Rubim?

96

Czy ktoś może mi powiedzieć co

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

jest i robi?

Christian Bankester
źródło
2
Czy to cała linia kodu? To znaczy, czy nie ma nic przed „wysłaniem”?
żyrafa

Odpowiedzi:

107

send wysyła komunikat do instancji obiektu i jego przodków w hierarchii klas, dopóki jakaś metoda nie zareaguje (ponieważ jej nazwa pasuje do pierwszego argumentu).

Praktycznie rzecz biorąc, te wiersze są równoważne:

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

Zauważ, że sendpomija sprawdzanie widoczności, więc możesz wywoływać również metody prywatne (przydatne do testowania jednostkowego).


Jeśli tak naprawdę nie ma zmiennej przed wysłaniem, oznacza to, że używany jest obiekt globalny:

send :to_s    # "main"
send :class   # Object
żyrafa
źródło
1
Rozumiem, więc można by użyć send, gdyby ktoś chciał zapisać w bazie danych coś takiego jak 1. miesiąc, zamiast statycznego podawania liczby dni.
Christian Bankester
3
To prawda, możesz go użyć do wywołania metody z obliczonymi nazwami, a nie statycznymi. (Nie powinieneś jednak zezwalać na nieograniczone wprowadzanie danych przez użytkownika, aby uniknąć wywoływania metod prywatnych ... Możesz jednak nadać im unikalny prefiks: send 'user_method _' + nazwa metody, * args)
giraff
2
Dobry przypadek użycia może być taki, że chcesz przetestować metodę klasy chronionej, możesz wywołać ją poza klasą - w pliku testowym ..
GN.
108

send jest metodą ruby ​​(bez szyn) pozwalającą na wywołanie innej metody po nazwie.

Z dokumentacji

   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
źródło
5
Świetna odpowiedź, wyraźniejsza niż pełna, zaakceptowana odpowiedź.
kodowanie aaron
63

Myślę, że jedną z najbardziej przydatnych funkcji metody .send jest to, że może ona dynamicznie wywoływać metodę. Może to zaoszczędzić wiele pisania. Jednym z najpopularniejszych zastosowań metody .send jest dynamiczne przypisywanie atrybutów. Na przykład:

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

Aby regularnie przypisywać atrybuty, należałoby to zrobić

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

Lub używając metody .send:

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

Ale to wszystko można zastąpić następującymi:

Zakładając, że aplikacja Rails musi przypisać atrybuty do klasy samochodu na podstawie danych wejściowych użytkownika, możesz to zrobić

c = Car.new()
params.each do |key, value|
  c.send("#{key}=", value)
end
Antonio Jha
źródło
Dzięki za świetny link
sid_09
7
Użycie .send w ten sposób dodaje niepotrzebnej złożoności i ułatwia nieumyślne wprowadzenie błędu do kodu. Na przykład w powyższym kodzie, jeśli dodasz nowy wpis do skrótu parametrów (taki jak „cylindry”), kod zakończy się niepowodzeniem z niezdefiniowanym błędem metody.
Kevin Schwerdtfeger
1
odpowiedzi na? w razie potrzeby można by użyć do zapobiegania takim błędom.
Richard_G
1
To było świetne wyjaśnienie! Dzięki Jha!
Sharath
1
@Kevin masz rację, ale czasami może to być konieczne. Większa elastyczność wiąże się z większym ryzykiem, które można złagodzić.
Will Sheppard
12

Inny przykład, podobny do https://stackoverflow.com/a/26193804/1897857 Antonio Jha

jest wtedy, gdy chcesz odczytać atrybuty obiektu.

Na przykład, jeśli masz tablicę ciągów, jeśli spróbujesz iterować przez nie i wywołać je na swoim obiekcie, to nie zadziała.

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

Możesz sendjednak napisać ciągi do obiektu:

atts = ['name', 'description']
@project = Project.first
atts.each do |a|
  puts @project.send(a)
end
# => Vandalay Project
# => A very important project
Mike Vallano
źródło
Dzięki za proste i łatwe wyjaśnienie!
Junan Chakma
Dzięki! To jest dokładnie odpowiedź, której szukam. Zastanawiasz się, czy jest to powszechnie używane? Natknąłem się na coś podobnego w starym kodzie, nie jestem pewien, czy powinienem się tego trzymać. @ Mike Vallano
B Liu
1
@ b-liu Widziałem, że jest on używany przez doświadczonych programistów w nowym kodzie. Może być również pomocny przy korzystaniu z define_method: apidock.com/ruby/Module/define_method .
Mike Vallano
Niesamowite! Dzięki! @MikeVallano
B Liu
4

Co robi wysyłanie?

send to inny sposób na wywołanie metody.

Najlepiej ilustruje to przykład:

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

Send żyje w klasie Object .

Jaka jest korzyść z tego?

Zaletą tego podejścia jest to, że jako parametr można przekazać metodę, którą chcesz wywołać. Oto prosty przykład:

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

Możesz przekazać metodę, którą chcesz wywołać. W tym przypadku przeszliśmy :to_s. Może to być bardzo przydatne przy metaprogramowaniu w języku ruby, ponieważ pozwala nam to wywoływać różne metody zgodnie z naszymi różnymi wymaganiami.

BKSpurgeon
źródło
0

Inny przypadek użycia widoków:

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

Dopuszczać . możesz napisać skalowalny widok, który pracuje z wszelkiego rodzaju obiektami z:

    render 'your_view_path', object: "my_object"
JustAnotherRubyLover
źródło
Dodaje to niepotrzebną logikę do widoków i może mieć wpływ na bezpieczeństwo. Nie rób tego. Używaj tablic i skrótów.
Derrek Bertrand