2016-08-02 93 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