在 Ruby 中创建 lambda 引用实例方法的哈希



我有一个类,它带有指向该类中方法的lambda哈希。

Thing.new.call
=> undefined local variable or method `do_foo' for Thing:Class

我在等

Thing.new.call
=> foo
=> bar

lambda似乎调用do_foodo_bar作为类方法,而不是实例方法。我能修一下吗?我不想使用send(command),因为命令可以由用户更新,并且可能存在黑客攻击风险。

链接到replit

class Thing
def initialize
@commands = [:foo, :bar]
end

SAFE_COMMANDS = {
foo: -> { do_foo },
bar: -> { do_bar }
}  
def call
@commands.each { |command| SAFE_COMMANDS[command].call }
end
private
def do_foo 
puts 'foo'
end
def do_bar
puts 'bar'
end
end

这里的问题是SAFE_COMMANDS数组和其中的lambda是在类上下文中定义的,因此它试图调用类方法。

您可以将实例传递给lambdas,以便调用其实例方法。

class Thing
def initialize
@commands = [:foo, :bar]
end

SAFE_COMMANDS = {
foo: -> (obj){ obj.do_foo },
bar: -> (obj){ obj.do_bar }
}  
def call
@commands.each { |command| SAFE_COMMANDS[command].call(self) }
end
private
def do_foo 
puts 'foo'
end
def do_bar
puts 'bar'
end
end

然而,你会得到以下错误:

private method `do_foo' called for #<Thing:0x00007fdc018399f8 @commands=[:foo, :bar]> (NoMethodError)

这是因为这些方法是私有的,因此Lambda无法访问。

以下是我的建议。使用#send,但首先过滤命令,如果发出非法命令,则引发异常(或类似情况(。

class Thing
def initialize
@commands = [:do_foo, :do_bar, :do_bad_stuff, :do_really_bad_stuff]
end

SAFE_COMMANDS = %i{
do_foo
do_bar
}
def call
illegal_commands = @commands - SAFE_COMMANDS
raise("illegal commands: #{illegal_commands.join(', ')}") unless illegal_commands.empty?
@commands.each { |command| self.send(command) }
end
private
def do_foo 
puts 'foo'
end
def do_bar
puts 'bar'
end
end

这将创建一个不在SAFE_COMMANDS数组中的任何命令的数组。如果该数组为空,那么我们只收到了合法的命令,然后继续。否则,我们会引发一个异常,并很好地格式化这些非法命令,告诉用户他们"搞砸了">

illegal commands: do_bad_stuff, do_really_bad_stuff (RuntimeError)

但我建议在初始化时检查一下。我想您最终会以某种方式将这些命令传递给实例,所以我进行了更改。

class Thing
def initialize( *commands )
commands = commands.map(&:to_sym)
illegal_commands = commands - SAFE_COMMANDS
raise("illegal commands: #{illegal_commands.join(', ')}") unless illegal_commands.empty?
@commands = commands
end

SAFE_COMMANDS = %i{
do_foo
do_bar
}
def call
@commands.each { |command| self.send(command) }
end
private
def do_foo 
puts 'foo'
end
def do_bar
puts 'bar'
end
end
# the exception is raised here
thing = Thing.new(:do_foo, :do_bar, :do_bad_stuff, :do_really_bad_stuff)
# rather than here
thing.call

我认为用lambdas过滤命令列表而不是恶作剧是方式更安全的安全方式,也不那么复杂。

最新更新