2016-08-02 90 views
0

例如調用我有兩個方法的類:動態方法與參數

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

    end 
    def method_two 

    end 
end 

和方法在控制,我打電話給他們:

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

但問題是當我嘗試調用method_two

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

這是因爲params[:value]返回nil。 最簡單的解決方法是:

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 

我不知道是否有什麼更好的解決方法,不傳遞參數,如果它是空。

回答

2

什麼你正在嘗試做的可真危險,所以我建議你先篩選的params[:method]

allowed_methods = { 
    method_one: ->(ex){ex.method_one(params[:value])} 
    method_two: ->(ex){ex.method_two} 
} 
allowed_methods[params[:method]]&.call(ex) 

我定義的哈希映射方法名拉姆達調用該方法,它處理的論點和你想要的任何特殊情況。

如果params[:method]位於allowed_methods散列中,我只能得到一個lambda作爲關鍵字。

&.語法是紅寶石2.3新的安全導航操作,以及 - 短 - 如果接收者不是零(即的allowed_methods[params[:method]]結果) 如果你不使用Ruby> = 2.3,執行下面的方法,你可以使用try代替,這在這種情況下,類似的行爲:

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

如果不過濾的params[:method]值,則用戶可以只通過:destroy例如刪除您的條目,該條目當然不是你想要的。

另外,通過調用ex.send ...,可以繞過對象的封裝,通常不應該這樣做。要僅使用公共接口,請使用public_send


對你的代碼的大的安全漏洞還有一點:

evalObject(從Kernel實際上繼承)定義的私有方法,所以你可以使用它的任何對象上這樣說:

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

,與您的代碼,想象用戶將eval作爲params[:method]價值和params[:value]任意Ruby代碼,他可以真正地whateve他希望在你的應用程序中。

+0

我在那邊想着安全問題,但我不知道'eval'的情況。像'update','destroy'等傳遞方法對我來說並不重要,因爲這是我想允許用戶做的事情之一。 我有一個問題。你爲什麼不用'allowed'方法凍結hash? – Gregy

+0

我推薦使用類似於我在答案中第一次寫的內容,它會更安全,並按照每種方法處理參數的數量。 – Geoffroy

+0

'allowed_methods'絕對是一個好主意,但是這個實現是超設計的完美例子。 '%i | method_one method_two |'就夠了。使用相同數量的參數的訣竅是一個非常糟糕的主意:它破壞了SRP原理並基本上使這些代碼不可支持。 – mudasobwa

1

如果你知道你在做什麼,有更容易的解決方法:

def method_two _ = nil 
end 

def method_two * 
end 

它的工作原理,以及其他方式輪:

def method_one *args 
end 
def method_two * 
end 

和:

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

旁註:寧願public_send超過send,除非您明確地調用private方法。


使用splatted PARAMS不修改方法的簽名:

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

我想避免修改方法。你能解釋一下這兩種增加是什麼嗎? – Gregy

+1

上面的所有代碼片段,除了第一個代碼片段外,都使用[splatted params](https://endofline.wordpress.com/2011/01/21/the-strange-ruby-splat/)。如果你想用不同的顯式簽名來調用不同的方法,你必須執行一個檢查或者使用棘手的'ex.public_send(* [params [:method],params [:value]]。compact)'。後者將就地刪除'nil'參數。 – mudasobwa