开发者

How do I dynamically add an attr_reader

开发者 https://www.devze.com 2023-02-21 11:00 出处:网络
I expect the following code to work as expected but it gives me a NoMethodError (private method `foo\' called for #<MyClass...)

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
0

精彩评论

暂无评论...
验证码 换一张
取 消