在 class 定义中使用方法参数 (Ruby)

Use method arguments inside class definition (Ruby)

我正在尝试向所有对象添加一个方法,该方法允许动态向实例添加变量。但是,我有 运行 问题,当我开始 class 块时,方法参数超出范围,因此不再可用。

如何从 class 块中访问方法参数?或者有更好的方法吗?

class Object
    def addvar(name, value = nil)
        puts name # => "test"
        class << self
            puts name # => returns nil!
            attr_accessor name # => nil is not a symbol nor a string (TypeError)
        end
        self.instance_variable_set("@" + name.to_s, value)
    end
end

x = Object.new
x.addvar("test", 3)

也可以将 eval 与字符串替换一起使用,但出于安全原因,我宁愿避免这样做:

class Object
    def addvar(name, value = nil)
        eval("class << self
                  attr_accessor :#{name}
             end
             self.#{name} = #{value}")
    end
end

x = Object.new
x.addvar(:test, 3)
puts x.test

您可能需要 instance_variable_set, instance_variable_get 和 Ruby 中的其他元编程方法。老实说,这就是 attr_accessor 在幕后使用的东西。

以下代码通过使用 define_singleton_method:

向调用 add_var 方法的对象添加单例方法
class Object
  def addvar(name, value = nil)
    instance_name = "@#{name}"

    instance_variable_set(instance_name, value)

    define_singleton_method(name) do
      instance_variable_get(instance_name)
    end

    define_singleton_method("#{name}=") do |new_value|
      instance_variable_set(instance_name, new_value)
    end
  end
end

x = Object.new
x.addvar("test1", 3)
p x.test1

p Object.new.test1

=> 3
=> 1.rb:20:in `<main>': undefined method `test1' for #<Object:0x007fc98b161e10> (NoMethodError)

如果要为 class 的任何对象声明方法,可以在 class 上使用 define_method:

class Object
  def addvar(name, value = nil)
    instance_name = "@#{name}"

    instance_variable_set(instance_name, value)

    self.class.send(:define_method, name) do
      instance_variable_get(instance_name)
    end

    self.class.send(:define_method, "#{name}=") do |new_value|
      instance_variable_set(instance_name, new_value)
    end
  end
end

x = Object.new
x.addvar("test1", 3)
p x.test1

p Object.new.test1

=> 3
=> nil

顺便说一下,你仍然可以使用 attr_accessor,你可以在 class 对象上调用它:

class Object
  def addvar(name, value = nil)
    instance_variable_set("@#{name}", value)

    self.class.send(:attr_accessor, name)
  end
end

x = Object.new
x.addvar("test1", 3)
p x.test1

x.test1 = 5
p x.test1

p Object.new.test1