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

时间:2017-10-09 05:53:10

标签: ruby metaprogramming

我正在尝试向所有对象添加一个方法,允许动态地向实例添加变量。但是,我遇到的问题是,当我开始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

1 个答案:

答案 0 :(得分:1)

您可能需要使用instance_variable_setinstance_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)

如果要为类的任何对象声明方法,可以在类上使用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 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
相关问题