Ruby 使用块在父范围中设置变量

Ruby 使用块在父范围中设置变量,ruby,metaprogramming,Ruby,Metaprogramming,gem提供了一个如何在父作用域中设置变量的示例: (这是刚从自述中粘贴的) 这很有用,但由于需要在字符串中进行所有变量初始化,因此受到限制 我仔细考虑了一下,意识到我可以使用YAML来序列化/反序列化对象 以下面的例子为例: def c value = YAML.dump [ { a: "b" } ] binding.of_caller(2).eval("var = YAML.load('#{value}')") end a() # => {a: "b"} 这很酷,但如果我可以

gem提供了一个如何在父作用域中设置变量的示例:

(这是刚从自述中粘贴的)

这很有用,但由于需要在字符串中进行所有变量初始化,因此受到限制

我仔细考虑了一下,意识到我可以使用YAML来序列化/反序列化对象

以下面的例子为例:

def c
  value = YAML.dump [ { a: "b" } ]
  binding.of_caller(2).eval("var = YAML.load('#{value}')")
end

a()
# => {a: "b"}
这很酷,但如果我可以完全避免序列化,只编写一个适当的
do;结束像这样的块:

# doesnt work
def c
  binding.of_caller(2).eval do
    # ideally this would set the variable named "var" in the scope of method "a"
    var = [ { a: "b" } ]
  end
end

如何实现最后一个示例的功能?如果有其他方法,我不需要使用调用者的绑定

这是我能做的最好的了,而且,我怀疑(尽管我真的很想被证明是错误的),除了编写自己的C扩展名a la binding_of_调用者之外,你能找到的最好的方法是:

require 'binding_of_caller'

module BindingExtensionEvalBlock
  def eval_block(&block)
    eval("ObjectSpace._id2ref(%d).call(binding)" % block.object_id)
  end
end

class ::Binding
  include BindingExtensionEvalBlock
end
当然,魔法就在这里:

eval("ObjectSpace._id2ref(%d).call(binding)" % block.object_id)
我们获取进程的对象ID,然后在我们的
绑定#eval
中,使用从内存中的任何位置检索它并调用它,传入本地
绑定

这就是它的作用:

def a
  var = 10
  b
  puts var
end

def b
  c
end

def c
  binding.of_caller(2).eval_block do |bnd|
    bnd.local_variable_set(:var, [ { a: "b" } ])
  end
end

a # => {:a=>"b"}

如您所见,在我们的块中,我们必须执行
bnd.local\u variable\u set(:var,[{a:b}])
,而不是
var=[{a:b}]
。在Ruby中无法更改块的绑定,因此我们必须传入一个绑定(
bnd
)并调用。

你有问题吗?@Jordan I添加了一个明确的问题
def a
  var = 10
  b
  puts var
end

def b
  c
end

def c
  binding.of_caller(2).eval_block do |bnd|
    bnd.local_variable_set(:var, [ { a: "b" } ])
  end
end

a # => {:a=>"b"}