2009-06-09 10 views
1

Ich habe eine Klasse, die einige private Attribute enthält. Was ich tun möchte, ist, einige Setter für diese dynamisch nur für die Ausführung eines bestimmten Blocks hinzuzufügen.Erweitern für einen Block Anruf nur

Beispiel von dem, was Ich mag sein wäre in der Lage:

class Content 
    attr_reader :a, :b 

    def initialize 
    @a = 1 
    @b = "plop" 
    end 

    def set(&block) 
    extend(Setter) 
    instance_eval(&block) 
    unextend(Setter) ???? 
    end 

    module Setter 
    def a(value) 
     @a = value 
    end 
    def b(value) 
     @b = value 
    end 
    end 
end 

content = Content.new 
content.set do 
    a 2 
    b "yeah!" 
end 
content.a # should return 2 

EDIT: Vielen Dank für die großen Antworten so weit. Ich klärte die Frage, weil ich tatsächlich Attributleser in der Klasse selbst definieren muss, die mit den im Modul definierten Settlern in Konflikt geraten können. Ich habe diesen Teil vergessen, als ich die Frage gepostet habe. (Es war spät ^^)

CLARIFICATION: Diese Klasse ist für eine DSL gedacht, um eine Konfigurationsdatei zu schreiben. Es richtet sich an Nicht-Entwickler, also je weniger Betreiber, desto besser.

Ich implementiere dies derzeit mit einer Proxy-Klasse, die instance_eval den Block, aber ich habe mit instance_variable_set zu verwirren, um die Werte einzustellen und ich mag es nicht. Ich versuche nur einen anderen Weg, um zu sehen, ob ich meinen Code lesbarer machen kann.

+0

Gibt es einen Grund, warum Sie benutzerdefinierte Schriftsteller gerade schreiben , anstatt attr_writer zu verwenden, um sie für dich zu generieren? – rampion

Antwort

2
def set(&block) 
    extend(Setter) 
    instance_eval(&block) 
    Setter.instance_methods.each do |m| 
     instance_eval "undef #{m}" 
    end 
    end 

Ich weiß nicht jede Methode, die für Sie tun würde, obwohl es etwas sein könnte .. Dies sollte jedoch die Arbeit tun, indem sie alle Instanzmethoden Setter zu finden und sie in Inhalt undefining.

+0

Effizienter zu tun instance_eval {undef m}. – Chuck

+0

funktioniert nicht, weil undef nimmt 'm' als Literal, anstatt es zu bewerten ... obwohl man instance_eval {undef: "# {m}"} tun könnte, aber ich denke nicht, dass das viel klarer ist. – cloudhead

+0

Oh, Entschuldigung. Sollte undef_method gewesen sein, aber dann müssten Sie die Singleton-Klasse aufrufen, um sie aufzurufen. Es wäre wahrscheinlich immer noch viel schneller. – Chuck

3

Es gibt keine native Möglichkeit, Module in Ruby zu "entpacken". Der Edelstein mixology implementiert dieses Muster als C (und Java, für JRuby) -Erweiterung und erstellt die Methoden mixin und unmix. Es scheint, dass Sie möglicherweise eine patch anwenden müssen, wenn Sie jedoch Unterstützung von Ruby 1.9 benötigen.

Wenn Sie lieber vermeiden Bibliotheken von Drittanbietern verwenden, kann ein anderer Ansatz einfach sein, die Setter privat:

class Content 
    def initialize 
    @a = 1 
    @b = "plop" 
    end 

    def set(&block) 
    instance_eval(&block) 
    end 

    private 

    def a(val) 
    @a = val 
    end 

    def b(val) 
    @b = val 
    end 

end 

content = Content.new 

#This will succeed 
content.set do 
    a 2 
    b "yeah!" 
end 

# This will raise a NoMethodError, as it attempts to call a private method 
content.a 3 
+0

Danke für den privaten Trick, wusste ich nicht darüber. Leider brauche ich auch Attribut-Leser in meiner ursprünglichen Klasse und sie würden mit gemischten Attributen in Konflikt geraten. Ich werde die Frage klären, weil ich verstehe, dass dies nicht immer trivial ist. –

0

, wenn Sie in einem experimentellen Stimmung Gefühl überprüft auch heraus: dup_eval

Es ist in mancher Hinsicht ähnlich mixico aber mit einigen interessanten Extras (object2module)