2010-07-14 6 views
4

(Big edit, j'ai eu une partie du chemin ...) J'ai piraté et je suis venu avec cela comme un moyen de spécifier les choses qui doivent être faites avant les attributs sont lus:Personnalisation de attr_reader pour faire l'instanciation paresseuse des attributs

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 

Mais changethings n'est pas reconnue comme une méthode - quelqu'un at-il des idées?

+0

Il ressemble à mon bloc attr_reader est en cours d'exécution dans le cadre de l'objet de classe, plutôt que c'est par exemple. Des idées sur la façon de le forcer à l'instance? –

+0

Vous pouvez utiliser 'instance_eval' à la place de yield, avec block comme paramètre. Mais ça sent un peu. –

Répondre

3

Vous devez évaluer le bloc dans le contexte de l'instance. Par défaut, yield l'évaluera dans son contexte natif.

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

Cela fait le tour magnifiquement! –

1

Voici une autre approche alternative que vous pouvez regarder. Ce n'est pas aussi élégant que ce que vous essayez de faire en utilisant define_method mais il vaut peut-être la peine de regarder.

Ajouter une nouvelle méthode 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 

utiliser ensuite comme ceci:

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

    def name_loader 
    @name = "Bob" 
    end 
end 

en action:

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

à mon humble avis changeant le contexte du bloc est assez contre-intuitif, du point de vue de quelqu'un qui utiliserait cette attr_reader sur les stéroïdes.

Peut-être que vous devriez considérer plaine ol » « indiquer le nom de la méthode en utilisant des arguments optionnels » approche:

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