将块传递到 define_method
Pass block into define_method
问题
有个模式是我自己用的比较频繁的,想晒一下。我有这样的东西:
class InfoGatherer
def foo
true
end
def people
unless @people
@people = # Long and complex calculation (using foo)
end
@people
end
end
我想把它晒干成这样:
class InfoGatherer
extend AttrCalculator
def foo
true
end
attr_calculator(:people) { # Long and complex calculation (using foo) }
end
为此,我定义了一个模块 AttrCalculator
以扩展到 InfoGatherer
。这是我尝试过的:
module AttrCalculator
def attr_calculator(variable_name_symbol)
variable_name = "@#{variable_name_symbol}"
define_method variable_name_symbol do
unless instance_variable_defined?(variable_name)
instance_variable_set(variable_name, block.call)
end
instance_variable_get(variable_name)
end
end
end
不幸的是,当我尝试像 InfoGatherer.new.people
这样简单的操作时,我得到:
NameError: undefined local variable or method `foo' for InfoGatherer:Class
嗯,这很奇怪。为什么 block
运行 在 InfoGatherer:Class
的范围内,而不是它的实例 InfoGatherer.new
?
研究
我知道我不能使用 yield
,因为那样会尝试捕获错误的块,如 here 所示。
我试图用 self.instance_exec(block)
代替上面的 block.call
,但后来我收到一个新错误:
LocalJumpError: no block given
嗯?我在 this SO question 中看到了同样的错误,但我已经在使用括号表示法,所以那里的答案似乎并不适用。
我也尝试过使用 class_eval
,但我不确定如何在字符串中调用 block
。这肯定行不通:
class_eval("
def #{variable_name_symbol}
unless #{variable_name}
#{variable_name} = #{block.call}
end
#{variable_name}
end
")
该用例称为记忆。可以像这样轻松完成:
def people
@people ||= # Long and complex calculation (using foo)
end
你不应该像现在这样陷入困境。
扩展最后的人
def people(varariable = nil)
@people ||= ComplexCalculation.new(variable).evaluate
end
class ComplexCalculation
def initialize(variable)
@variable = variable
end
def evaluate(variable)
#stuff
end
end
通过提取此 class,您可以隔离复杂性并获得更好的体验。
问题是,在 define_method
中,self
令人惊讶地 InfoGatherer
,而不是 InfoGatherer
的 实例 ].所以我在 self.instance_exec(block)
.
的正确轨道上
有效的解决方案是self.instance_exec(&block)
(注意和号)。我想解释器不会识别 block
是一个块,除非你这样标记它?如果有人能比我更好地解释这一点,请这样做。
附带说明一下,这不是解决此特定问题的最佳方法。请参阅@sawa 的回答,了解记忆复杂计算的简洁方法。
问题
有个模式是我自己用的比较频繁的,想晒一下。我有这样的东西:
class InfoGatherer
def foo
true
end
def people
unless @people
@people = # Long and complex calculation (using foo)
end
@people
end
end
我想把它晒干成这样:
class InfoGatherer
extend AttrCalculator
def foo
true
end
attr_calculator(:people) { # Long and complex calculation (using foo) }
end
为此,我定义了一个模块 AttrCalculator
以扩展到 InfoGatherer
。这是我尝试过的:
module AttrCalculator
def attr_calculator(variable_name_symbol)
variable_name = "@#{variable_name_symbol}"
define_method variable_name_symbol do
unless instance_variable_defined?(variable_name)
instance_variable_set(variable_name, block.call)
end
instance_variable_get(variable_name)
end
end
end
不幸的是,当我尝试像 InfoGatherer.new.people
这样简单的操作时,我得到:
NameError: undefined local variable or method `foo' for InfoGatherer:Class
嗯,这很奇怪。为什么 block
运行 在 InfoGatherer:Class
的范围内,而不是它的实例 InfoGatherer.new
?
研究
我知道我不能使用 yield
,因为那样会尝试捕获错误的块,如 here 所示。
我试图用 self.instance_exec(block)
代替上面的 block.call
,但后来我收到一个新错误:
LocalJumpError: no block given
嗯?我在 this SO question 中看到了同样的错误,但我已经在使用括号表示法,所以那里的答案似乎并不适用。
我也尝试过使用 class_eval
,但我不确定如何在字符串中调用 block
。这肯定行不通:
class_eval("
def #{variable_name_symbol}
unless #{variable_name}
#{variable_name} = #{block.call}
end
#{variable_name}
end
")
该用例称为记忆。可以像这样轻松完成:
def people
@people ||= # Long and complex calculation (using foo)
end
你不应该像现在这样陷入困境。
扩展最后的人
def people(varariable = nil)
@people ||= ComplexCalculation.new(variable).evaluate
end
class ComplexCalculation
def initialize(variable)
@variable = variable
end
def evaluate(variable)
#stuff
end
end
通过提取此 class,您可以隔离复杂性并获得更好的体验。
问题是,在 define_method
中,self
令人惊讶地 InfoGatherer
,而不是 InfoGatherer
的 实例 ].所以我在 self.instance_exec(block)
.
有效的解决方案是self.instance_exec(&block)
(注意和号)。我想解释器不会识别 block
是一个块,除非你这样标记它?如果有人能比我更好地解释这一点,请这样做。
附带说明一下,这不是解决此特定问题的最佳方法。请参阅@sawa 的回答,了解记忆复杂计算的简洁方法。