我可以在Ruby中针对Binding对象评估一个块吗?

时间:2013-06-18 00:38:14

标签: ruby exception-handling

我正在尝试将引发异常的上下文传递给Exception子类,以便使用此上下文生成信息性错误消息。我知道我可以将各个数据传递给Exception,但我想知道如何传递和使用整个上下文。

我知道我可以使用Kernel#binding捕获上下文。类Binding的Ruby 2.0文档列出了一个方法:eval。这使您可以评估String捕获的上下文中的Binding。我想知道是否有可能在绑定的上下文中评估块。我会用这个如下:

class MyError < StandardError

  def initialize(str: nil, context: nil)
    @str = str; @context = context
    super(str)
  end

  def to_s
    @str ? @str : @context.**SOME_METHOD** { "Error: x == #{x}" }
  end

end 

x = 5
raise MyError.new(context: binding)

=> Error: x == #{5}

对于 SOME_METHOD ,我已经尝试了instance_execinstance_eval,两者都不起作用。有没有办法做到这一点?或者,有一些原因是从整个上下文生成错误消息是一个坏主意,而不是来自环境的单个数据片段?

1 个答案:

答案 0 :(得分:3)

使用eval(string, binding)运行带有给定变量绑定的Ruby代码。对于您的情况,您可以重写to_s方法,如下所示:

def to_s
  @str ? @str : eval('"Error: x == #{x}"', @context)
end

请注意,在将参数传递给'"some_string"'之前,您需要使用eval表单来阻止字符串插值。

如果要使用某种绑定来评估块,则必须将块分配给变量,并eval将块分配给block.call。现在您有两个绑定环境,一个用于变量block,另一个用于块中的变量。由于join没有combineBinding这样的方法,因此您似乎无法通过Kernel#evalBinding#eval实现此目标。此外,一个块捕获它定义的绑定。当您调用eval('block.call', some_binding)时,它会忽略传入的绑定。

但是,您可以使用Object#instance_eval并使用instance_eval(&block)形式传递块,这将在Class的实例方法的绑定中评估块。因此,您可以使用捕获的上下文为块创建正确的绑定。

class BlockEnv
  def initialize(context, &block)
    # remove instance methods inherited from Object to minimize the impact
    # remove this if unnecessary
    BlockEnv.instance_methods
      .reject{|m|
        [:object_id, :call, :instance_eval, :method_missing].include?(m) ||
          m.to_s !~ /^[a-z]\w*$/i }
      .each do |m|
        eval("undef :#{m}")
      end
    @context = context
    @block = block
  end
  def call
    self.instance_eval(&@block)
  end
  def method_missing(name)
    eval(name.to_s, @context) rescue super
  end
end

class MyError < StandardError
  def initialize(str: nil, context: nil)
    @str = str; @context = context
    super(str)
  end

  def to_s
    @str ? @str : BlockEnv.new(@context){ "Error: x == #{x}" }.call
  end
end

x = 5
raise MyError.new(context: binding)