【问题标题】:How to use a hash in a class_eval statement in Ruby如何在 Ruby 的 class_eval 语句中使用哈希
【发布时间】:2017-03-22 07:15:01
【问题描述】:

当我遇到一个令人沮丧的问题时,我正在做家庭作业。该作业是 Ruby 元编程中的一个练习,目标是定义一个“attr_accessor_with_history”,它与“attr_accessor”做所有相同的事情,但还提供一个属性曾经存在的所有值的历史记录。以下是作业中提供的代码以及我为完成作业而添加的一些代码:

    class Class

  def attr_accessor_with_history(attr_name)
    attr_name = attr_name.to_s
    attr_hist_name = attr_name+'_history'
    history_hash = {attr_name => []}

    #getter
    self.class_eval("def #{attr_name} ; @#{attr_name} ; end")
    #setter
    self.class_eval %Q{
      def #{attr_name}=(val)
        # add to history
        @#{attr_hist_name} = [nil] if @#{attr_hist_name}.nil?
        @#{attr_hist_name} << val
        history_hash[@#{attr_name}] = @#{attr_hist_name}

        # set the value itself
        @#{attr_name} = val
      end

      def history(attr) ; @history_hash[attr.to_s] ; end
    }
  end
end

class Foo
  attr_accessor_with_history :bar
  attr_accessor_with_history :crud
end
f = Foo.new     # => #<Foo:0x127e678>
f.bar = 3       # => 3
f.bar = :wowzo  # => :wowzo
f.bar = 'boo!'  # => 'boo!'
puts f.history(:bar) # => [3, :wowzo, 'boo!']
f.crud = 42
f.crud = "Hello World!"
puts f.history(:crud)

我想使用散列来存储不同属性的不同历史记录,但我无法在 setter 的 class_eval 语句中访问该散列。无论我如何尝试设置它,我似乎总是为 []= 方法获得 NoMethodError,因为“history_hash”以某种方式变成 NilClass 类型,或者发生 NameError,因为它将“history_hash”视为未定义的局部变量或方法。如何在 class_eval 语句中使用哈希?

【问题讨论】:

  • 局部变量history_hash和实例变量@history_hash是不同的变量。
  • 也就是说,当attr_accessor_with_history()方法在这行之后执行完毕:attr_accessor_with_history :bar,局部变量history_hash被破坏,导致尝试访问时报错:undefined local variable or method 'history_hash' history_hash 在这一行的bar=() 设置器中:f.bar = 3我想使用哈希来存储不同属性的不同历史记录--将要持久保存在局部变量中的数据永远行不通。

标签: ruby hash metaprogramming class-eval


【解决方案1】:

或发生 NameError 是因为它将“history_hash”视为未定义的局部变量或方法

我会说你不能,因为它一个局部变量,在你想要的上下文中是不可访问的。但是,为什么你甚至需要它?我有理由确定它在“我为完成分配而添加的一些代码”中,而不是原始分配代码(我假设它希望您将 @bar 的历史记录存储在 @bar_history 中 - 或否则attr_hist_name 到底是什么?)

我也对字符串评估感到不舒服;通常没有必要,而 Ruby 可以通过其强大的元编程工具做得更好。这是我的做法:

class Class
  def attr_accessor_with_history(attr_name)
    attr_setter_name = :"#{attr_name}="
    attr_getter_name = :"#{attr_name}"
    attr_hist_name = :"@#{attr_name}_history"
    attr_name = :"@#{attr_name}"

    self.class_eval do
      define_method(attr_getter_name) do
        instance_variable_get(attr_name)
      end

      define_method(attr_setter_name) do |val|
        instance_variable_set(attr_name, val)
        history = instance_variable_get(attr_hist_name)
        instance_variable_set(attr_hist_name, history = []) unless history
        history << val
      end
    end
  end
end

class Object
  def history(attr_name)
    attr_hist_name = :"@#{attr_name}_history"
    instance_variable_get(attr_hist_name)
  end
end

最后,由于它是猴子补丁基类,我宁愿在需要的地方使用改进来添加它,但这对于分配来说可能有点过头了。

【讨论】:

  • 我有哈希的原因是因为 f.history(:bar) 东西在那里,我想不出另一种方法来存储不同属性的历史记录。我希望我的一些代码是不必要的,因为我尝试了几种不同的东西。不过谢谢,这很有帮助。
猜你喜欢
  • 1970-01-01
  • 2016-11-14
  • 1970-01-01
  • 2012-02-06
  • 1970-01-01
  • 2012-03-30
  • 2011-06-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多