Как динамически добавить attr_reader

Я ожидаю, что следующий код будет работать так, как ожидалось, но он дает мне NoMethodError (частный метод `foo ', вызываемый для # < MyClass...)

class MyClass
end

my_object = MyClass.new

my_object.instance_variable_set(:@foo, "bar")
MyClass.send("attr_reader", :foo)

puts my_object.foo

Проблема заключается в том, что я использую буквально идентичный код в более крупном приложении, и он работает точно так, как я ожидаю, но когда я упрощу его на этот самый простой пример, он терпит неудачу.

(Я понимаю, что есть много других способов сделать то, что я делаю в Ruby)

Ответ 1

Используйте Module # class_eval:

Таким образом, блок помещает вас в контекст MyClass, что позволяет вам вызвать 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" 

Ответ 2

Интересная проблема, я нашел, что это решение работает нормально:

MyClass.class_eval("attr_reader :foo")

Ответ 3

Ответ от @MikeLewis хорош, но почему бы не просто повторно открыть класс?

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

Ответ 4

Предположим на мгновение, что вы хотите создать accessor для этого конкретного экземпляра класса (который, как я предполагаю, имеет место, поскольку вы работаете с экземпляром.

Вы можете просто открыть экземпляр singleton class и использовать instance_eval для добавления 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

Ответ 5

Просто запустите метод, чтобы он стал общедоступным:

MyClass.send("public", :foo)

Я не знаю, почему это частное в некоторых случаях.