2009-05-02 7 views
1

Ich habe eine ziemlich eindeutige Klasse, die es ihren untergeordneten Klassen ermöglicht, virtuelle Felder zu deklarieren. Das Kind kann virtuelle Felder als XML gespeichert deklarieren, indem ein Verfahren der Elternklasse wie diese Aufruf:Wie rufen Sie attr_accessible dynamisch in Rails auf?

class Child1 < Parent 
    create_xml_field ["readings", "usage"] 
end

Ich habe es geschafft, um es um über eine unangenehme Arbeit zu arbeiten. Die create_xml_field Methode speichert die Feldnamen in der Klassenvariablen (siehe unten). Die init_xml_fields Methode wird von innerhalb der after_initialize Methode aufgerufen.

class Parent < ActiveRecord::Base 

    def self.create_xml_field(fields) 
    @@xml_fields[self.name] = fields 
    end 

    def init_xml_fields(xml_fields) 
    xml_fields.each do |f| 
     f=f.to_sym 
     self.class_eval do 
     define_method(f) { ... } # define getter 
     define_method(f) { ... } # define setter 
     attr_accessible(f)  # add to mass assign OK list, does not seem to work! 
     end 
    end 
    end 

    protected 
    def after_initialize 
     init_xml_fields 
    end 
end

Nasty genug? Ich bin nicht stolz, aber ich habe Probleme, es zum Laufen zu bringen. Außerdem funktioniert die Umgehung nicht mit der Massenzuweisung von Formularparametern.

Hat jemand Erfahrung, attr_accessible dynamisch aufzurufen, um Massenzuweisungen in der Kindklasse zuzulassen? Vielen Dank im Voraus!

Dieser Beitrag wurde zur besseren Lesbarkeit bearbeitet!

+0

Das ist alles sehr verwirrend, aber es scheint mir, dass Sie eine Instanz Methode sind dem Aufruf der Klasse zu ändern, die eine Eigenklasse für diese bestimmte Instanz schaffen würde. Es würde helfen, wenn Sie ein minimales Beispiel mit Ihren tatsächlichen Klassen gepostet haben. Was Sie versuchen, ist sehr einfach, aber Sie scheinen es aus einem etwas falschen Winkel zu nähern. – kch

+0

Einverstanden, es ist verwirrend. Tut mir leid, ich konnte es nicht besser erklären! Vielen Dank für Ihre Antwort. Ich poste noch mehr Code, um das Szenario besser zu beschreiben. – pchap10k

+0

Okay, ich habe dein Beispiel verdüstert. Es ist klar, dass ich einige runde Stifte in quadratischen Löchern hatte. Nehmen wir als Beispiel Ihr Beispiel: Wie kann ich die Deklaration ** add_yaml_fields: foo,: bar ** in der Kindklasse statt in der Elternklasse deklarieren? Auch mein Elternteil hat mehrere Kinder, so verursacht die ** write_inheritable_array (: yaml_fields, ...) ** Deklaration Probleme zwischen Child1, Child2, etc ...? Vielen Dank im Voraus, und ja YAML ist schön. – pchap10k

Antwort

1

So würde ich den Metaprogrammierungsteil implementieren, der die Zugriffsmethoden erstellt und sie als attr_accessibles einstellt.

Ich benutze YAML intead von XML nur als ein persönlicher Kreuzzug. Ich ging sogar voran und implementierte den nicht benötigten Serialisierungsteil, nur um dich zu YAML zu bewegen.

require 'yaml' 
require 'rubygems' 
require 'active_support' 
require 'active_record' 

module Yamlable 
    def self.included m 
    m.extend ClassMethods 
    end 

    module ClassMethods 
    def add_yaml_fields *args 
     write_inheritable_array(:yaml_fields, args) 
     attr_accessor(*args) 
     attr_accessible(*args) 
     before_save :serialize_yaml_fields 
    end 
    end 

    def serialize_yaml_fields 
    self.yamlable_column = read_inheritable_attribute(:yaml_fields)\ 
     .inject({}) { |h, a| h[a] = send(a); h }.to_yaml 
    end 

    def initialize(*args) 
    super 
    YAML::load(yamlable_column).each { |k, v| send("#{k}=", v) } 
    end 
end 

class ParentModel < ActiveRecord::Base 
    include Yamlable 
    add_yaml_fields :foo, :bar 
end 

class ChildModel < ParentModel 
end 

# look, they're there: 
y ChildModel.read_inheritable_attribute(:yaml_fields) 

Nun, wenn Sie wissen wollen, warum Ihre speziellen Code nicht funktioniert, werden Sie mehr davon schreiben müssen.


Ich sollte wahrscheinlich ein wenig auf Klasse vererbbare Attribute erweitern. Sie sind ein wenig wie Klassenvariablen, ein bisschen wie Klasseninstanzvariablen.

Wenn Sie ein vererbbares Attribut in einer Klasse definieren, wird es von allen Unterklassen gemeinsam genutzt. Wenn Sie jedoch das Attribut in einer untergeordneten Klasse aktualisieren, kopiert diese untergeordnete Klasse das ursprüngliche Attribut und aktualisiert es, sodass die Aktualisierungen ausschließlich für dieses Attribut gelten und andere Klassen in der Erbschaftskette nicht davon betroffen sind.

Mit der normalen Methode write_inheritable_attribute überschreiben Sie den Wert des übergeordneten Elements einfach durch Setzen auf eine untergeordnete Klasse. Mit vererbbaren Arrays und Hashes wird write_inheritable_* concat/merge mit den Werten der Elternklasse.


So in der Praxis meine add_yaml_fields funktioniert wie folgt:

class Parent 
    add_yaml_attributes :foo 

class Child1 < Parent 
    add_yaml_attributes :bar 

class Child2 < Parent 
    add_yaml_attributes :baz 

Damit Attribute der yaml für jede Klasse sein wird:

  • Parent: foo
  • Child1 : foo, bar
  • Kind2: foo, baz
+0

Das scheint auf dem richtigen Weg zu sein, aber ich bin mir nicht sicher, wie ich das in meine Elternklasse integrieren soll, und Ihr Beispiel nennt "add_yaml_fields" vom Elternteil und nicht vom Kind. Vielen Dank für zusätzlichen Rat. – pchap10k

+0

Das Yamlable-Modell kann in jede Klasse aufgenommen werden und es werden add_yaml_fields für es und alle seine Nachkommen bereitgestellt. Um es im ChildModel aufzurufen, rufen Sie es einfach dort und nicht im übergeordneten Element auf. Keine Tricks erforderlich. – kch

+0

Vielen Dank für den erneuten Versuch! Ich habe es funktioniert nach dem Ändern der "yamable_column" in "content", die spezifisch für meine DB-Tabelle ist. Gute Arbeit, ich habe dir die richtige Antwort gegeben (und es gab so viel Konkurrenz!) Prost Kumpel. – pchap10k

0

@kch ist korrekt, jedoch habe ich ein Problem mit Initialisierung (* Args) gefunden.ActiveRecord instanziiert Modellobjekte nicht immer unter Verwendung von new(), so dass die Methode initialize() nicht immer aufgerufen wird.

Verwenden Sie stattdessen after_initialize (* args) wie unten gezeigt.

def self.included m 
    m.extend ClassMethods 
    end 

    module ClassMethods 
    def add_yaml_fields *args 
     write_inheritable_array(:yaml_fields, args) 
     attr_accessor(*args) 
     attr_accessible(*args) 
     before_save :serialize_yaml_fields 
    end 
    end 

    def serialize_yaml_fields 
    self.yamlable_column = read_inheritable_attribute(:yaml_fields)\ 
     .inject({}) { |h, a| h[a] = send(a); h }.to_yaml 
    end 

    def after_initialize(*args) 
    super 
    YAML::load(yamlable_column).each { |k, v| send("#{k}=", v) } 
    end 
end 

class ParentModel < ActiveRecord::Base 
    include Yamlable 
    add_yaml_fields :foo, :bar 
end 

class ChildModel < ParentModel 
end 
Verwandte Themen