使用参数调用动态方法

时间:2016-08-02 10:24:47

标签: ruby-on-rails ruby dynamic-programming

例如,我有两个方法的课程:

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

我想知道是否有更好的解决方法,如果它为null,则不传递参数。

2 个答案:

答案 0 :(得分:3)

您尝试做的事情可能非常危险,因此我建议您先前过滤params[:method]

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

我定义了一个Hash,将方法名称映射到一个lambda,调用该方法,该方法处理参数和你想要的任何特殊情况。

如果params[:method]散列中的allowed_methods作为密钥,我只会得到一个lambda。

&.语法是ruby 2.3中新的安全导航操作符,并且 - 简而言之 - 如果接收者不是nil,则执行以下方法(即allowed_methods[params[:method]]的结果) 如果您不使用ruby&gt; = 2.3,则可以使用try代替,在这种情况下具有类似行为:

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

如果您没有过滤params[:method]的值,那么用户可以通过:destroy来删除您的条目,这肯定不是您想要的。

此外,通过调用ex.send ...,您可以绕过对象的封装,这通常是不应该的。要仅使用公共界面,请使用public_send

关于代码的重大安全漏洞的另一点:

eval是在Object上定义的私有方法(实际上是从Kernel继承而来的),因此您可以在任何对象上使用它:

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

现在,使用您的代码,假设用户将eval作为params[:method]的值和params[:value]中的任意ruby代码,他实际上可以在您的应用程序中执行任何他想要的操作。

答案 1 :(得分: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])

Sidenote :除非您明确调用public_send方法,否则首选send而不是private

使用splatted params而不修改方法签名:

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