2015-01-16 8 views
7

Ich versuche, hier auf dem Prinzip der geringsten Überraschung zu arbeiten ...Ruby-Semantik für die Annahme eines Objekts oder dessen ID als Argument

Angenommen, Sie haben eine Methode haben, die zwei Objekte akzeptiert. Die Methode benötigt diese Objektinstanzen, aber an der Stelle, an der Sie die Klasse initialisieren, können Sie nur Referenz-IDs haben. Dies wäre beispielsweise in einem Router/Controller in einem Web-Service üblich. Die Einrichtung könnte etwa so aussehen:

post "/:foo_id/add_bar/:bar_id" do 
    AddFooToBar.call(...) 
end 

Es gibt viele verschiedene Möglichkeiten, dass dies gelöst werden könnte. Für mich ist das die meisten ‚idomatic‘ hier ist so etwas wie dieses:

def AddFooToBar.call(foo:nil,foo_id:nil,bar:nil,bar_id:nil) 
    @foo = foo || Foo[foo_id] 
    @bar = bar || Bar[bar_id] 
    ... 
end 

Dann, wenn Sie die Methode aufrufen, können Sie es gerne nennen könnte:

AddFooToBar.call(foo: a_foo, bar: a_bar) 
AddFooToBar.call(foo_id: 1, bar_id: 2) 

Dies schafft eine ziemlich klare Schnittstelle, aber die Umsetzung ist etwas ausführlich, besonders wenn es mehr als 2 Objekte gibt und deren Namen länger sind als foo und bar.

Sie könnten eine gute altmodische Hash verwenden, anstatt ...

def AddFooToBar.call(input={}) 
    @foo = input[:foo] || Foo[ input[:foo_id] ] 
    @bar = input[:bar] || Bar[ input[:bar_id ] 
end 

Die Methodensignatur ist super einfach jetzt, aber es verliert viel Klarheit im Vergleich zu dem, was Sie Keyword-Argumente erhalten werden.

Sie könnten nur einen einzigen Schlüssel zu verwenden, sondern vor allem, wenn beide Eingänge erforderlich sind:

def AddFooToBar.call(foo:,bar:) 
    @foo = foo.is_a?(Foo) ? foo : Foo[foo] 
    @bar = bar.is_a?(Bar) ? bar : Bar[bar] 
end 

Die Signatur der Methode ist einfach, obwohl es ein wenig seltsam ist nur eine ID übergeben Sie mit dem gleichen Argument name‘ Übergeben Sie eine Objektinstanz an. Die Suche in der Methodendefinition ist auch etwas hässlicher und weniger leicht zu lesen.

Sie könnten nur noch entscheiden, nicht das überhaupt zu internalisieren und erfordern die Anrufer-Instanzen initialisiert werden, bevor sie in.

vorbei
post "/:foo_id/add_bar/:bar_id" do 
    foo = Foo[ params[:foo_id] ] 
    bar = Bar[ params[:bar_id] ] 
    AddFooToBar.call(foo: foo, bar: bar) 
end 

Dies ist ganz klar, aber es bedeutet, dass jeder Ort, der die Methode aufruft, muss wissen, wie die erforderlichen Objekte zuerst initialisiert werden, anstatt die Option zu haben, dieses Verhalten in der Methode zu kapseln, die die Objekte benötigt.

Schließlich könnten Sie die Umkehrung durchführen und nur Objekt-IDs zulassen, um sicherzustellen, dass die Objekte in der Methode nachgeschlagen werden. Dies kann jedoch doppelte Nachschlagevorgänge zur Folge haben, falls Sie manchmal Instanzen haben, die Sie weitergeben möchten. Es ist auch schwieriger zu testen, da Sie nicht einfach einen Schein injizieren können.

Ich denke, das ist ein ziemlich häufiges Problem in Ruby, insbesondere beim Erstellen von Webdiensten, aber ich konnte nicht viel darüber schreiben. Meine Fragen sind also:

  1. Welche der oben genannten Ansätze (oder etwas anderes) würden Sie als konventioneller Ruby erwarten? (0)

  2. Gibt es noch andere Probleme oder Probleme in Bezug auf einen der obigen Ansätze, die ich nicht aufzählte, welche beeinflussen sollte, welche am besten funktioniert, oder Erfahrungen, die Sie dazu geführt haben, eine Option über die zu wählen Andere?

Vielen Dank!

+0

Interessante Frage. Meine Intuition würde (möglicherweise) sagen, eine Art faules Proxy-Objekt für ein Foo zu verwenden, das mit einer ID erstellt werden kann, die bei der Verwendung abgerufen wird. Dies scheint der Ansatz zu sein, der von allen Datenbank-Frameworks verwendet wurde, die ich in anderen Sprachen verwendet habe, und es ist ziemlich schmerzlos zu verwenden (obwohl es ziemlich ausführlich im Hintergrund ist, bin ich mir sicher). – Linuxios

Antwort

1

Ich würde gehen mit dem Erlauben entweder der Objekte oder der IDs undeutlich. Allerdings würde ich nicht tun, wie du getan hast:

def AddFooToBar.call(foo:,bar:) 
    @foo = foo.is_a?(Foo) ? foo : Foo[foo] 
    @bar = bar.is_a?(Bar) ? bar : Bar[foo] 
end 

In der Tat, ich verstehe nicht, warum Sie Bar[foo] und nicht Bar[bar] haben. Aber neben diesem, würde ich die Bedingungen stellt im [] Verfahren eingebaut:

def Foo.[] arg 
    case arg 
    when Foo then arg 
    else ...what_you_originally_had... 
    end 
end 

Dann würde ich die Methode in Frage muß wie definiert werden:

def AddFooToBar.call foo:, bar: 
    @foo, @bar = Foo[foo], Bar[bar] 
end 
+1

'Bar [foo]' war ein Tippfehler. Das tut mir leid! – Andrew

Verwandte Themen