2013-04-07 11 views
12

że mam obiekt z metody, która ma dostęp do obiektu:Ruby zagnieżdżone wysłać

def foo 
    @foo 
end 

wiem, że mogę używać wysłać do dostępu do tej metody:

obj.send("foo") # Returns @foo 

Czy istnieje prosty sposób nie rekurencyjnego send uzyskać parametr na obiekcie @foo, jak:

obj.send("foo.bar") # Returns @foo.bar 
+3

'obj.send ('foo'). Send ('bar')' –

+0

@SergioTulentsev - Przepraszam, powinienem był wyjaśnić - czy istnieje sposób zrób to w jednym kroku. To, co opisujesz, wymagałoby przeanalizowania napisu "foo.bar", aby zdać sobie sprawę, że musisz wywoływać rekurencyjnie send(). Właśnie zastanawiałem się, czy istnieje jakiś mechanizm, który robi to automatycznie lub bardziej czysto. – Lynn

+1

@Lynn: Nie jestem świadomy takiego wbudowanego mechanizmu. –

Odpowiedz

18

można użyć instance_eval:

obj.instance_eval("foo.bar") 

Można nawet uzyskać dostęp do zmiennej instancji bezpośrednio:

obj.instance_eval("@foo.bar") 
+1

bardzo mylące w '" @ foo.bar ", co to jest' bar'? Gdzie jest on zdefiniowany? –

+1

Jest to metoda 'bar' wywołana na obiekcie przechowywanym w' @ foo' .To dokładnie tak samo, jak wpisałeś to w Metoda instancji '@ foo' Sprawdź dokumentację –

+2

' instance_eval' ciągu jest niebezpieczne, kosztowne i bardzo rzadko wymagane – dbenhur

13

Podczas OP już zaakceptowane odpowiedź korzystając instance_eval(string), Gorąco zachęcam OP aby uniknąć formy ciągów o eval chyba że jest to absolutnie konieczne. Eval wywołuje kompilator ruby ​​- jest on drogi w obliczeniach i niebezpieczny w użyciu, ponieważ otwiera wektor do ataków z użyciem kodu.

Jak stwierdził że nie ma potrzeby wysyłania w ogóle:

obj.foo.bar 

Jeśli rzeczywiście nazwy foo i bar pochodzą z jakiegoś non-obliczeniach statycznych, a następnie

obj.send(foo_method).send(bar_method) 

jest proste i wszystko trzeba na to.

Jeżeli metody idą w postaci przerywanej ciąg, można użyć rozłamu i wstrzyknąć do łańcucha metody:

'foo.bar'.split('.').inject(obj, :send) 

wyjaśnieniu w odpowiedzi na komentarze: eval ciąg jest jednym z najbardziej ryzykownych jednej rzeczy może zrobić z perspektywy bezpieczeństwa. Jeśli istnieje jakikolwiek sposób, w jaki łańcuch jest zbudowany z danych wprowadzonych przez użytkownika bez niewiarygodnie dokładnej kontroli i sprawdzania poprawności tego wejścia, powinieneś po prostu wziąć pod uwagę swój system.

send (metoda), w której metoda jest uzyskiwana z danych wprowadzanych przez użytkownika, również jest zagrożona, ale istnieje bardziej ograniczony wektor ataku. Wprowadzane przez użytkownika dane mogą spowodować wykonanie dowolnej metody 0-arghument, którą można wysłać za pośrednictwem odbiornika. Dobra praktyka tutaj byłoby zawsze białej listy metod przed wysłaniem:

VALID_USER_METHODS = %w{foo bar baz} 
def safe_send(method) 
    raise ArgumentError, "#{method} not allowed" unless VALID_USER_METHODS.include?(method.to_s) 
    send(method) 
end 
+0

Łańcuch wywołań 'send' opartych na łańcuchach nie jest dużo bezpieczniejszy niż użycie' eval'. –

+0

Zgadzam się z obydwoma sentymentami na bezpieczeństwo: IMHO, Dodałbym białą listę tego, co można nazwać zarówno ze względów bezpieczeństwa, jak i mitygowania deweloperów.) Tworzysz dziurę w zlewie, więc bądź ostrożny wokół tego. – timpone

+0

@dbenhur - To Michael's komentarz - nie są wysyłane i wstrzykiwać tak źle, jak eval pod względem zagrożenia bezpieczeństwa? Lub jest jeden preferowany nad drugim? – Lynn

1

Trochę późno do partii, ale musiałem zrobić coś podobnego, który miał łączyć zarówno „wysyłanie” i dostęp do danych z hash/tablicy w jednym wywołaniu.W zasadzie to pozwala zrobić coś jak poniżej

value = obj.send_nested("data.foo['bar'].id")

i pod maską będzie to coś podobnego do

obj.send(data).send(foo)['bar'].send(id)

ta działa również z symbolami w ciągu atrybutu

value = obj.send_nested('data.foo[:bar][0].id')

które zrobi coś podobnego do

obj.send(data).send(foo)[:bar][0].send(id)

W przypadku, gdy chcesz używać obojętny dostępu można dodać, że jako parametr, jak również. Na przykład.

value = obj.send_nested('data.foo[:bar][0].id', with_indifferent_access: true)

Ponieważ jest to nieco bardziej zaangażowane, here is the link to the gist że można użyć tej metody, aby dodać do bazy Ruby Object. (Obejmuje to również testy, dzięki którym można zobaczyć, jak to działa)