【问题标题】:Ruby nested sendRuby 嵌套发送
【发布时间】:2013-03-29 13:15:28
【问题描述】:

假设我有一个带有访问对象的方法的对象:

def foo
   @foo
end

我知道我可以使用 send 来访问该方法:

obj.send("foo")  # Returns @foo

有没有一种直接的方法来进行递归发送以获取@foo 对象上的参数,例如:

obj.send("foo.bar")  # Returns @foo.bar

【问题讨论】:

  • obj.send('foo').send('bar')
  • @SergioTulentsev - 对不起,我应该澄清一下 - 有没有办法一步完成。您所描述的将需要解析他的字符串“foo.bar”以意识到您需要递归调用 send() 。我只是想知道是否有一些现有的机制可以自动或更干净地做到这一点。
  • @Lynn:我不知道这种内置机制。
  • 是的,我认为"foo.bar".split('.').inject(obj) { |obj, property| obj.send(property) } 是你能得到的最接近的。
  • @PascalJungblut 可以简化为"foo.bar".split(".").inject(obj, :send)

标签: ruby metaprogramming


【解决方案1】:

你可以使用instance_eval:

obj.instance_eval("foo.bar")

你甚至可以直接访问实例变量:

obj.instance_eval("@foo.bar")

【讨论】:

  • "@foo.bar 中非常混乱bar 是什么?它是在哪里定义的?
  • 这是对存储在@foo 中的对象调用的方法bar。这与您在@foo 的实例方法中键入此内容完全相同。查看文档。
  • instance_eval 的字符串是危险的、昂贵的,而且很少需要。
【解决方案2】:

虽然 OP 已经接受了使用 instance_eval(string) 的答案,但我强烈敦促 OP 避免使用 eval 的字符串形式,除非绝对必要。 Eval 调用 ruby​​ 编译器——它的计算成本很高,使用起来也很危险,因为它会打开一个向量进行代码注入攻击。

如上所述,根本不需要发送:

obj.foo.bar

如果 foo 和 bar 的名称确实来自一些非静态计算,那么

obj.send(foo_method).send(bar_method)

很简单,所有人都需要。

如果方法以点分字符串的形式出现,可以使用 split 和 injection 来链接方法:

'foo.bar'.split('.').inject(obj, :send)

针对 cme​​ts 进行澄清:从安全角度来看,字符串 eval 是人们可以做的最危险的事情之一。如果有任何方式从用户提供的输入构造字符串,而没有对该输入进行非常勤奋的检查和验证,那么您应该只考虑您的系统拥有。

send(method) 从用户输入获取方法也有风险,但攻击向量更有限。您的用户输入可能会导致您执行任何可通过接收器调度的 0 参数方法。这里的良好做法是始终在调度之前将方法列入白名单:

VALID_USER_METHODS = %w{foo bar baz}
def safe_send(method)
  raise ArgumentError, "#{method} not allowed" unless VALID_USER_METHODS.include?(method.to_s)
  send(method)
end

【讨论】:

  • 基于字符串的send 调用链并不比使用eval 更安全。
  • 我同意这两种关于安全的观点。恕我直言,出于明确的安全原因和开发人员的维护,我会将可以称为的内容列入白名单。你正在创建一个水槽,所以要小心它周围。
  • @dbenhur - 对迈克尔的评论 - 就安全风险而言,发送和注入不如 eval 糟糕吗?还是一个比另一个更可取?
  • @MichaëlWitrant injhdeed,基于输入的发送应该通过白名单得到适当的祝福。尽管如此,与字符串 eval 相比,它的表面积要小得多。攻击向量仅限于当前接收者及其祖先的无参数方法。
【解决方案3】:

聚会有点晚了,但我不得不做一些类似的事情,必须在一次调用中结合“发送”和访问来自哈希/数组的数据。基本上,这允许您执行以下操作

value = obj.send_nested("data.foo['bar'].id")

在引擎盖下,这将做一些类似于

obj.send(data).send(foo)['bar'].send(id)

这也适用于属性字符串中的符号

value = obj.send_nested('data.foo[:bar][0].id')

这将做一些类似于

obj.send(data).send(foo)[:bar][0].send(id)

如果您想使用无关访问,您也可以将其添加为参数。例如

value = obj.send_nested('data.foo[:bar][0].id', with_indifferent_access: true)

由于涉及更多,您可以使用here is the link to the gist 将该方法添加到基本 Ruby 对象。 (它还包括测试,以便您了解它是如何工作的)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-05-04
    • 2013-06-10
    • 2018-08-17
    • 2011-04-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多