在 class_eval 中访问 Ruby class 变量的顶级错误

Toplevel error accessing Ruby class variable in class_eval

我正在处理旧代码 Rails 3,Ruby 1.9.3 代码库。我引用了 gem 由于各种原因我无法升级。 gem 已经使用 class_eval 进行了猴子修补(正确的术语?)。我需要更改另一个看起来像这样的方法:

SomeNamespace::Bar do
  def some_method
    @@part_of_header ||= JSON.dump({... Stuff ...})

    # ... other code ...

    headers = {
      "Header Part" => @@part_of_header
      #... other headers ...
    }
  end
end

@@part_of_header class 变量背后的想法是缓存 JSON 转储以便可以重复使用。 @@part_of_header 未在 Bar base class.

的其他地方定义

我的猴子修补方法如下:

SomeNamespace::Bar.class_eval do
  def some_method
    @@part_of_header ||= JSON.dump({... Stuff ...})

    # ... other code that I changed ...

    headers = {
      "Header Part" => @@part_of_header
      #... other headers that I changed ...
    }
  end
end

代码工作正常,但我在 @@part_of_header class 变量的行中收到以下警告:

Class variable access from toplevel

我尝试将 class 变量移动到它自己的方法中:

SomeNamespace::Bar.class_eval do
  def header_part
    @@part_of_header ||= JSON.dump({... Stuff ...})
  end

  def some_method
    # ... other code that I changed ...

    headers = {
      "Header Part" => header_part
      #... other headers that I changed ...
    }
  end
end

然而 "toplevel" 错误只是转移到 header_part 方法。

我还尝试使用 class_variable_set 和 class_variable_get 访问 class 变量,但出现未定义的方法错误。

关于如何修复此警告的任何建议?如果无法修复,关于在 class_eval 中缓存 JSON 转储的任何建议?谢谢。

更新: 感谢@Josh 使用完整的 class 名称和 class_variable_get/set。我的最终解决方案如下:

SomeNamespace::Bar.class_eval do
  def header_part
    # Create the class variable if it does not exist, remember
    # the base class does not define @@part_of_header.
    if !SomeNamesapce::Bar.class_variable_defined?(:@@part_of_header)
      SomeNamespace::Bar.class_variable_set(:@@part_of_header, nil)
    end

     if (SomeNamespace::Bar.class_variable_get(:@@part_of_header).nil?
       header_part = JSON.dump({... Stuff ...})
       SomeNamespace::Bar.class_variable_set(:@@part_of_header, header_part)
     end

     SomeNamespace::Bar.class_variable_get(:@@part_of_header)
  end

  def some_method
    # ... other code that I changed ...

    headers = {
      "Header Part" => header_part
      #... other headers that I changed ...
    }
  end
end

以上方法有效,但如有任何对上述解决方案的反馈,我们将不胜感激。谢谢。

看起来问题在于,尽管 class_eval do 块是在 SomeNamespace::Bar 的上下文中执行的,但该上下文不适用于对 [=26] 的引用=]变量。

如果您 explicitly access the class variable,那么事情应该会按预期进行:

# NOTE: Omitting conditional set (||=) for simplicity
SomeNamespace::Bar::class_variable_set(:@@part_of_header, JSON.dump({... Stuff ...}))

headers = {
  "Header Part" => SomeNamespace::Bar::class_variable_get(:@@part_of_header)
  #... other headers that I changed ...
}

如果 @@part_of_header 真的只在 some_method 中使用,并且如果你完全替换 some_method,那么你使用自己的模块变量 / class 变量,而不是重用现有的 SomeNamespace::Bar::@@part_of_header。我可能更喜欢这种方法;感觉它更好地封装了您的更改。

module MonkeyPatch
  SomeNamespace::Bar.class_eval do
    def some_method
      # This is within the MonkeyPatch module, so it
      # makes a new class variable for MonkeyPatch
      @@part_of_header ||= "JSON.dump({'a': 12})"

      # ... other code that I changed ...

      headers = {
        "Header Part" => @@part_of_header
        #... other headers that I changed ...
      }
      puts headers
    end
  end
end