撤消在 rendering_controller 上设置的实例变量 (render_anywhere gem)

Revoking instance variable set on rendering_controller (render_anywhere gem)

这个问题的这个特定用例可能会导致 github 问题,但我的问题比您将看到的特定用例更广泛,所以请耐心等待!

我在一个项目中使用 render_anywhere gem,我发现我需要撤销在下一次迭代发生之前在循环内部设置的实例变量(使用 render_anywhere 的对象存在于循环之外,因此变量保留在范围内)。

如果可能,我宁愿避免为循环的每次迭代重新初始化对象。

set_instance_variable 方法的工作原理如下所示(摘自 rubydoc):

# File 'lib/render_anywhere.rb', line 20

def set_instance_variable(var, value)
  rendering_controller.class_eval do
    attr_accessor :"#{var}"
  end
  rendering_controller.public_send("#{var}=", value)
end

所以我想我需要使用 this answer:

中描述的方法删除 attr_accessor
undef :"#{var}"
undef :"#{var}="

所以我的问题确实是双重的:

  1. 一旦设置了实例变量,上述方法是否是从 rendering_controller 中删除属性 read/write 方法的最佳方法?
  2. attr_accessor 会做其他 undef 不会 'clean up'
  3. 的事情吗

attr_accessor 是一个 "macro",它简单地为同名的 ivar 定义了 getter 和 setter 方法:

# This line...
attr_accessor :hello

# ...is equivalent to these
def hello
  @hello
end

def hello=(value)
  @hello = value
end

因此,您需要撤消的 set_instance_variable 代码的效果是:

  1. 正在创建 getter var
  2. 正在创建 setter var=
  3. 正在用 value 参数中的值初始化 ivar @var

请注意,set_instance_variable 方法在 控制器实例 上下文中执行所有这些操作,而不是控制器 class 上下文(尽管令人困惑 class_eval).

您必须执行类似这样的操作才能撤消上述所有操作:

def unset_instance_variable(var)
  rendering_controller.remove_instance_variable("@#{var}") if instance_variable_defined?("@#{var}")
  rendering_controller.class_eval { remove_method(var) } if rendering_controller.respond_to?(var)
  rendering_controller.class_eval { remove_method("#{var}=") } if rendering_controller.respond_to?("#{var}=")
end  

如果您想避免猴子补丁,请考虑将 rendering_controller 作为参数发送给此方法。