How do I dynamically add an attr_reader
I expect the following code to work as expected but it gives me a NoMethodError (private method `foo' called for #<MyClass...)
class MyClass
end
my_object = MyClass.new
my_object.instance_variable_set(:@foo, "bar")
MyClass.send("attr_reader", :foo)
puts my_object.foo
The problem is I'm using literally identical code in a larger application and it works exactly as I expect, but when I simplify it to this most basic e开发者_开发百科xample it fails.
(I understand there are many other ways to do what I'm doing in Ruby)
Use Module#class_eval:
By doing this, the block puts you in the context of MyClass, thus allowing you to call attr_reader
ruby-1.9.2-p136 :028 > my_object.instance_variable_set(:@foo, "bar")
=> "bar"
ruby-1.9.2-p136 :029 > MyClass.class_eval{attr_reader :foo}
=> nil
ruby-1.9.2-p136 :030 > my_object.foo
=> "bar"
Let's assume for an instant that you want to create an accessor for that particular instance of the class (which I assume is the case since you are operating on the instance.
You can simply open up the instance's singleton class and use instance_eval to add the accessor
class MyClass
end
my_instance = MyClass.new
my_instance.singleton_class.instance_eval { attr_accessor :foo }
my_instance.foo = :bar
my_instance.foo # => :bar
Interesting problem, I found this solution works fine:
MyClass.class_eval("attr_reader :foo")
The answer by @MikeLewis is nice, but why not just re-open the class?
irb(main):001:0> class MyClass; end
#=> nil
irb(main):002:0> m = MyClass.new
#=> #<MyClass:0x2d43ae0>
irb(main):003:0> class MyClass; attr_accessor :foo; end
#=> nil
irb(main):004:0> m.foo = 42
#=> 42
Just force the method to become public:
MyClass.send("public", :foo)
I have no idea why it is private in some cases.
I'm puzzled at this, but one solution that works is to make the attribute_reader explicitly public using MyClass.send(:public, :foo)
, i.e.
class MyClass
end
my_object = MyClass.new
my_object.instance_variable_set(:@foo, "bar")
MyClass.send(:attr_reader, :foo)
MyClass.send(:public, :foo)
puts my_object.foo
精彩评论