2010-07-14 3 views
4

(Большое изменение, я получил часть пути там ...) Я взломал прочь, и я придумал это как способ указать, что нужно сделать перед тем атрибуты чтения:Настройка attr_reader для создания ленивого создания атрибутов

class Class 
    def attr_reader(*params) 
    if block_given? 
     params.each do |sym| 
     define_method(sym) do 
      yield 
      self.instance_variable_get("@#{sym}") 
     end 
     end 
    else 
     params.each do |sym| 
     attr sym 
     end 
    end 
    end 
end 

class Test 
    attr_reader :normal 
    attr_reader(:jp,:nope) { changethings if @nope.nil? } 

    def initialize 
    @normal = "Normal" 
    @jp = "JP" 
    @done = false 
    end 

    def changethings 
    p "doing" 
    @jp = "Haha!" 
    @nope = "poop" 
    end 

end 

j = Test.new 

p j.normal 
p j.jp 

Но changethings не распознается как метод - кто есть какие-нибудь идеи?

+0

Похоже, мой attr_reader блок выполняется в рамках объекта класса, а не его экземпляр. Какие-нибудь идеи о том, как заставить это к экземпляру? –

+0

Вы можете использовать 'instance_eval' вместо yield, с блоком в качестве параметра. Но это немного пахнет. –

ответ

3

Вам необходимо оценить блок в контексте экземпляра. yield по умолчанию будет оценивать его в своем родном контексте.

class Class 
    def attr_reader(*params, &blk) 
    if block_given? 
     params.each do |sym| 
     define_method(sym) do 
      self.instance_eval(&blk) 
      self.instance_variable_get("@#{sym}") 
     end 
     end 
    else 
     params.each do |sym| 
     attr sym 
     end 
    end 
    end 
end 
+0

Это трюк красиво! –

1

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

Добавить новый метод lazy_attr_reader к Class

class Class 
    def lazy_attr_reader(*vars) 
    options = vars.last.is_a?(::Hash) ? vars.pop : {} 
    # get the name of the method that will populate the attribute from options 
    # default to 'get_things' 
    init_method = options[:via] || 'get_things' 
    vars.each do |var| 
     class_eval("def #{var}; #{init_method} if !defined? @#{var}; @#{var}; end") 
    end 
    end 
end 

Затем использовать его как это:

class Test 
    lazy_attr_reader :name, :via => "name_loader" 

    def name_loader 
    @name = "Bob" 
    end 
end 

В действии:

irb(main):145:0> t = Test.new 
=> #<Test:0x2d6291c> 
irb(main):146:0> t.name 
=> "Bob" 
1

ИМХО изменение контекста блок довольно нелогичный, с точки зрения того, кто будет использовать такие attr_reader на стероидах.

Возможно, вам следует рассмотреть простой ол»указать имя методы, используя необязательные аргументы" подход:

def lazy_attr_reader(*args, params) 
    args.each do |e| 
    define_method(e) do 
     send(params[:init]) if params[:init] && !instance_variable_get("@#{e}") 
     instance_variable_get("@#{e}") 
    end 
    end 
end 

class Foo 
    lazy_attr_reader :foo, :bar, :init => :load 

    def load 
    @foo = 'foo' 
    @bar = 'bar' 
    end 
end 

f = Foo.new 
puts f.bar 
#=> bar 
Смежные вопросы