2016-08-02 23 views
0

Zum Beispiel Aufruf Ich habe Klasse mit zwei Methoden:Dynamische Methode mit Argumenten

class Example < ActiveRecord::Base 
    def method_one(value) 

    end 
    def method_two 

    end 
end 

und Verfahren in der Steuerung, wo ich sie nenne:

def example 
    ex = Example.find(params[:id]) 
    ex.send(params[:method], params[:value]) if ex.respond_to?(params[:method]) 
    end 

Aber das Problem kommt, wenn ich versuche method_two anrufen

ArgumentError (wrong number of arguments (1 for 0)) 

Es geschieht, weil params[:value] kehrt nil. Die einfachste Lösung ist:

def example 
    ex = Example.find(params[:id]) 
    if ex.respond_to?(params[:method]) 
     if params[:value].present? 
     ex.send(params[:method], params[:value]) 
     else 
     ex.send(params[:method]) 
     end 
    end 
    end 

Ich frage mich, ob es eine bessere Problemumgehung nicht Argument übergeben, wenn es null ist.

Antwort

2

Was Sie versuchen können zu tun wirklich gefährlich, so empfehle ich Ihnen die params[:method] filtern, bevor.

Ich habe einen Hash definiert, der den Namen der Methode einem Lambda zuordnet, der die Methode aufruft, die Argumente und alle Sonderfälle behandelt, die Sie wollen.

Ich bekomme nur ein Lambda, wenn params[:method] in der allowed_methods Hash als Schlüssel ist.

Die &. Syntax ist der neue sichere Navigation Operator in Ruby 2.3, und - kurz - führt die folgende Methode, wenn der Empfänger nicht gleich Null ist (dh das Ergebnis allowed_methods[params[:method]]) Wenn Sie nicht ruby> = 2.3 unter Verwendung von Sie können try statt, die ein ähnliches Verhalten in diesem Fall benutzen:

allowed_methods[params[:method]].try(:call, ex) 

Wenn Sie den Wert von params[:method] nicht filtern, dann ein Benutzer nur :destroy zum Beispiel passieren kann Ihren Eintrag zu löschen, das ist sicherlich nicht was du willst.

Durch den Aufruf ex.send ... umgehen Sie auch die Kapselung des Objekts, die Sie normalerweise nicht sollten.Wenn Sie nur die öffentliche Schnittstelle verwenden möchten, verwenden Sie vorzugsweise .


Ein weiterer Punkt auf der großen Sicherheitslücke von Ihnen Code:

eval ist eine private auf Object definierte Methode (eigentlich von Kernel geerbt), so dass Sie es auf diese Weise auf einem beliebiges Objekt verwenden können:

object = Object.new 
object.send(:eval, '1+1') #=> 2 

Jetzt, mit Ihrem Code, vorstellen, der Benutzer eval als der Wert von params[:method] und einem beliebigen ruby-Code in params[:value] setzt, kann er eigentlich gar whateve Er möchte in Ihrer Anwendung.

+0

Ich dachte da über Sicherheit nach, aber ich kannte den Fall mit 'eval' nicht. Methoden wie 'update',' destroy' usw. zu übergehen, war für mich keine große Sache, weil es eines der Dinge war, die ich dem Benutzer erlauben wollte. Ich habe eine Frage. Warum frierst du Hash nicht mit allowed_methods ein? – Gregy

+0

Ich empfehle, etwas ähnliches zu verwenden, was ich zuerst in meiner Antwort schrieb, es wird sicherer sein und behandelt pro Methode die Anzahl der Parameter. – Geoffroy

+0

'allowed_methods' ist definitiv eine gute Idee, aber die Implementierung ist ein perfektes Beispiel für Over-Over-Design. '% i | method_one method_zwei |' ist genug. Der Trick, die gleiche Anzahl von Parametern zu verwenden, ist eine sehr schlechte Idee: Sie bricht das SRP-Prinzip und macht diesen Code im Grunde nicht tragbar. – mudasobwa

1

Wenn Sie verstehen, was Sie tun, gibt es einfachere Lösungen:

def method_two _ = nil 
end 

oder

def method_two * 
end 

Es funktioniert auch umgekehrt:

def method_one *args 
end 
def method_two * 
end 

und:

ex.public_send(params[:method], *[params[:value]]) \ 
    if ex.respond_to?(params[:method]) 

Nebennote: bevorzugen Sie über send, es sei denn, Sie rufen explizit private Methode.


Verwenden splatted params ohne die Methoden Signaturen zu ändern:

ex.public_send(*[params[:method], params[:value]].compact) 
+0

Ich möchte Modifizierungsmethoden vermeiden. Können Sie erklären, was beide Ergänzungen? – Gregy

+1

Alle obigen Schnipsel, abgesehen von der allerersten, verwenden [splatted params] (https://endofline.wordpress.com/2011/01/21/the-strange-ruby-splat/). Wenn Sie verschiedene Methoden mit unterschiedlichen expliziten Signaturen aufrufen möchten, müssen Sie eine Prüfung durchführen oder mit tricky 'ex.public_send (* [params [: method], params [: value]]. Compact)' arbeiten. Letzterer wird den 'nil'-Parameter an Ort und Stelle entfernen. – mudasobwa

Verwandte Themen