2009-08-25 10 views
5

To jest dla już istniejącego publicznego interfejsu API, którego nie mogę złamać, ale chcę go przedłużyć.Elegancki sposób pisania, symboli i tablic kaczych?

Obecnie metoda pobiera ciąg lub symbol lub cokolwiek innego, co ma sens, gdy przeszedł jako pierwszy parametr do send

chciałbym dodać możliwość wysyłania listę ciągów, symbole, et cetera . Mógłbym po prostu użyć is_a? Array, ale istnieją inne sposoby wysyłania list, a to nie jest bardzo ruby.

Będę nazywać map na liście, więc pierwszą zmianą będzie użycie respond_to? :map. Ale ciąg odpowiada również na :map, więc to nie zadziała.

+3

Nie ma nic wspólnego z twoim pytaniem, ale mam nadzieję, że przeprowadzasz pewne testy, zanim wprowadzisz dane użytkownika do metody wysyłania! Yikes. –

+0

dobry punkt. W naszym przypadku dokumenty wyraźnie stwierdzają, że parametr jest nazwą metody. To wystarczająco głęboki interfejs API, który powinien wystarczyć ... –

+0

To nie jest kwestia dokumentacji - to dziura w bezpieczeństwie. Jeśli przejdą one w odsłonie, drugi parametr wysyłania zostanie uruchomiony w powłoce. Czy to naprawdę chcesz? Lepiej przeanalizuj dane wejściowe w instrukcji sprawy, a następnie wyślij symbole swojej konstrukcji. –

Odpowiedz

6

Jak traktować je wszystkie jako Arrays? Zachowanie chcesz za String s jest taka sama jak dla Array zawierającego tylko że String:

def foo(obj, arg) 
    [*arg].each { |method| obj.send(method) } 
end 

[*arg] sztuczka działa, ponieważ operator ikona (*) zamienia pojedynczy element w siebie albo Array do listy inline jego elementów.

Później

Jest to w zasadzie tylko składniowo wersja słodzone lub Arnaud's answer, choć istnieją subtelne różnice, jeśli zdać Array zawierająca inne Array s.

Później jeszcze

Jest dodatkowa różnica mających do czynienia z foo „s wartości zwracanej. Jeśli zadzwonisz pod numer foo(bar, :baz), możesz być zaskoczony, że odzyskasz [baz]. Aby rozwiązać ten problem, można dodać Kestrel:

def foo(obj, arg) 
    returning(arg) do |args| 
    [*args].each { |method| obj.send(method) } 
    end 
end 

który zawsze powróci arg jak minął. Lub możesz zrobić returning(obj), aby móc połączyć się z numerami foo. Od ciebie zależy, jakiego rodzaju zachowania chcesz zwrócić.

+0

O wiele bardziej piękna niż moja oryginalna wiadomość. Nie mogę nigdy przyzwyczaić się do operatora *. – Arnaud

+0

[* arg] genialny! – Dmitry

0

Czy możesz zmienić zachowanie na podstawie parametru nazwa_klasy? To brzydkie, ale jeśli dobrze rozumiem, masz jedną metodę, do której będziesz przekazywać różne typy - musisz jakoś to rozróżnić.

Alternatywnie, wystarczy dodać metodę, która obsługuje parametr typu tablicy. To trochę inne zachowanie, więc dodatkowa metoda może mieć sens.

+0

Twój drugi komentarz jest lepszym rozwiązaniem, ale mam nadzieję na więcej od guru stackoverflow. Pierwszy z nich ma taki sam problem jak 'is_a? Array': użytkownik może wysłać ciąg, który nie jest 'String' lub listą, która nie jest' Tablicą'. –

+0

Nie jestem pewien, jak powszechne są ciągi inne niż ciągnące 'String'.spisy "bez tablic" są dość powszechne - wiele obiektów to "zmienne". Niestety, tak są łańcuchy ... –

0

Użyj Marshal do serializacji obiektów przed ich wysłaniem.

+0

Jestem zdezorientowany. 'send' to metoda, która wywołuje funkcję po nazwie w Ruby. Jak Marszałek może pomóc? –

1

Od Array i String oba są Enumerables, nie ma eleganckiego sposobu na powiedzenie "rzecz, która jest Enumberable, ale nie String", przynajmniej nie w sposób omawiany.

Co chciałbym zrobić, to kaczka typu dla przeliczalny (responds_to? :[]), a następnie użyć case oświadczenie, tak jak poniżej:

def foo(obj, arg) 
    if arg.respond_to?(:[]) 
    case arg 
    when String then obj.send(arg) 
    else arg.each { |method_name| obj.send(method_name) } 
    end 
    end 
end 

lub nawet czystsze:

def foo(obj, arg) 
    case arg 
    when String then obj.send(arg) 
    when Enumerable then arg.each { |method| obj.send(method) } 
    else nil 
    end 
end 
+0

Dzięki za wyjaśnienie rozszerzone, ale w pierwotnym pytaniu poprosiłem o coś czystszego niż 'is_a? Array'. Używając 'is_a? String' (lub wariant, jaki pokazałeś) łamie oryginalny interfejs API dla każdego, kto używa czegoś, co szeleści jak string dla 'send'. W rzeczywistości większość użytkowników używa zamiast tego 'Symbol'. Mogę użyć 'x.is_a? (String) || x.is_a? (Symbol) ', ale to nie jest bardzo ruby, i może złamać API dla niektórych moich użytkowników. –

0

Jeśli nie chcesz monkeypatch, po prostu masuj listę do odpowiedniego ciągu przed wysłaniem. Jeśli nie przeszkadza monkeypatching lub dziedziczenie, ale chcą zachować tę samą metodę Podpis:

class ToBePatched 
    alias_method :__old_takes_a_string, :takes_a_string 

    #since the old method wanted only a string, check for a string and call the old method 
    # otherwise do your business with the map on things that respond to a map. 
    def takes_a_string(string_or_mappable) 
     return __old_takes_a_string(string_or_mappable) if String === string_or_mappable 
     raise ArgumentError unless string_or_mappable.responds_to?(:map) 
     # do whatever you wish to do 
    end 
end 
0

Być może pytanie nie było wystarczająco jasne, ale sen noc pokazała mi dwie czyste sposoby Aby odpowiedzieć na to pytanie.

1: to_sym jest dostępny pod numerami String i Symbol i powinien być dostępny na wszystkich elementach podobnych do ciągów.

if arg.respond_to? :to_sym 
    obj.send(arg, ...) 
else 
    # do array stuff 
end 

2: wysyłanie rzutów TypeError po przejściu przez tablicę.

begin 
    obj.send(arg, ...) 
rescue TypeError 
    # do array stuff 
end 

Szczególnie podoba mi się # 2. Mam poważne wątpliwości, że którykolwiek z użytkowników starego API spodziewa się, że TypeError zostanie podniesiony tą metodą ...

+1

Pierwsze rozwiązanie ma sens, ponieważ send faktycznie oczekuje znaku. Ale mam nadzieję, że nie traktujesz poważnie drugiego rozwiązania. Wyjątki nie są przepływem sterowania. –

+0

@Sarah: losowe, ale myślę, że około 40% wszystkich twoich komentarzy i odpowiedzi na SO zawiera zdanie "Wyjątki nie są przepływem kontrolnym". – Telemachus

+0

@Telemachus - przynajmniej ostatnio. :) –

1

Załóżmy, że funkcja o nazwie func

chciałbym zrobić tablicę z parametrami z

def func(param) 
    a = Array.new 
    a << param 
    a.flatten! 
    func_array(a) 
end 

skończyć z wykonania czynność func_array dla tablic tylko

z func (” cześć świecie ") dostaniesz a.flatten! => ["cześć świat"] z func (["cześć", "świat"]) dostaniesz a.flatten! => ["cześć", "świat"]

2

Krytyczna szczegół, który został pominięty w wszystkie odpowiedzi: ciągi zrobić nie reagować na :map, więc najprostszą odpowiedzią jest oryginalne pytanie: wystarczy użyć respond_to? :map.

Powiązane problemy