6

Also dachte ich, ich hatte diese Arbeit letzte Nacht, könnte es geschworen haben. Jetzt geht es nicht mehr, und ich denke, es ist an der Zeit, um Hilfe zu bitten.Rubin auf Schienen dynamische Attributfelder von DB mit method_missing Probleme

Im definierende dynamischen Feldern in der Datenbank, semi EAV-Stil, und im Moment nur sagen, läßt ich dont care Ihre Meinungen zu hören, ob EAV ist eine gute Idee oder nicht :)

Sowieso Im es ein wenig zu tun anders als ich es in der Vergangenheit gemacht habe, im Grunde, wenn ein Attribut (oder Feld) hinzugefügt wird, erstelle ich eine Add-Spalte zu einer bestimmten Attributtabellenmigration und führe es aus (oder entferne es) - ANYWAYS, weil es einen Kategorie-Layer gibt in der Mitte sitzen, die die direkte Beziehung ist, in der alle Attribute definiert sind, kann ich den tatsächlichen Attributnamen nicht als Spaltennamen verwenden, da die Attribute kategoriespezifisch sind.

Also, wenn es hilft Ihnen

Entity 
    belongs_to :category 

    Category 
    has_many :entities 

    EntityAttribute 
    belongs_to :category 

    EntityAttributeValue 
    belongs_to :entity_attribute 
    belongs_to :entity   

und EAV-Tabelle umfasst horizontal zu visualisieren als neue Attribute erstellt werden, mit Spalten attribute_1 attribute_2 markiert, die die Werte für diese bestimmte Einheit enthalten.

Sowieso - Ich versuche, die Methoden dynamisch auf das Entity-Modell zu machen, kann ich @ entity.actual_attribute_name so nennen, anstatt @ entity.entity_attribute_value.field_5

Hier ist der Code, den ich Arbeit gedacht war - -

def method_missing(method, *args) 

     return if self.project_category.blank? 

     puts "Sorry, I don't have #{method}, let me try to find a dynamic one." 
     puts "let me try to find a dynamic one" 

     keys = self.project_category.dynamic_fields.collect {|o| o.name.to_sym } 

     if keys.include?(method) 
     field = self.project_category.dynamic_fields.select { |field| field.name.to_sym == method.to_sym && field.project_category.id == self.project_category.id }.first 
     fields = self.project_category.dynamic_field_values.select {|field| field.name.to_sym == method } 
     self.project_category_field_value.send("field_#{field.id}".to_sym, *args) 
     end 

    end 

Dann heute als ich zurück nach Code ging, erkannte ich, obwohl ich das Attribut in Schienen Konsole einstellen könnte, und es wäre das richtige Feld zurückkehren, wenn ich den Datensatz gespeichert wurde die EntityAttributeValue keine Aktualisierungen (dargestellt als self.project_category_field_value, oben.)

So nach dem Blick in es weiter sah es so aus, als ob ich nur eine before_update oder before_save Callback hinzufügen musste, um das Attribut manuell zu speichern, und das ist, wo ich bemerkte, in Callback, würde es den Callback method_missing ausführen, als ob das Objekt war Dupliziert werden (und das neue Objekt war Kopie des ursprünglichen Objekts), oder etwas, ich bin nicht ganz sicher. Aber zu irgendeinem Zeitpunkt während des Speichervorgangs oder davor verschwindet mein Attribut in Vergessenheit.

Also, ich denke, ich habe meine eigene Frage halb beantwortet, nachdem ich es eingegeben habe, muss ich eine Instanzvariable setzen und überprüfen, ob es am Anfang meiner method_missing-Methode existiert (richtig?) Vielleicht ist das nicht was Ich weiß es nicht, aber ich frage auch, ob es einen besseren Weg gibt zu tun, was ich versuche zu tun. Wenn method_missing eine schlechte Idee ist, dann erkläre bitte, warum ich, wenn ich Beiträge über Methodenverweise durchführe, hörte, dass einige Leute sie zuschlägten, aber keiner von diesen Leuten eine vernünftige Erklärung dafür lieferte, warum die fehlende Methode eine schlechte Lösung war .

Vielen Dank im Voraus.

Antwort

2

Das ist etwas ernsthaft intensive Programmierung in der method_missing Abteilung gehen. Was Sie haben sollten ist etwas mehr wie folgt:

Sie können dann versuchen, dies in zwei Teile zu brechen. Der erste Schritt ist das Erstellen einer Methode, die entscheidet, ob ein Aufruf mit einem bestimmten Namen behandelt werden kann, hier dynamic_attribute_method_for, und die zweite Methode ist die aktuelle Methode.Die Aufgabe des ersteren besteht darin, sicherzustellen, dass letzteres zu dem Zeitpunkt funktioniert, zu dem es aufgerufen wird, möglicherweise unter Verwendung von define_method, um zu vermeiden, dass all dies beim nächsten Zugriff auf denselben Methodennamen erneut durchlaufen werden muss.

Diese Methode könnte wie folgt aussehen:

def dynamic_attribute_method_for(name) 
    dynamic_attributes = ... 

    type = :reader 

    attribute_name = name.to_s.sub(/=$/) do 
    type = :writer 
    '' 
    end 

    unless (dynamic_attributes.include?(attribute_name)) 
    return 
    end 

    case (type) 
    when :writer 
    define_method(name) do |value| 
     # Whatever you need 
    end 
    else 
    define_method(name) do 
     # Whatever you need 
    end 
    end 

    name 
end 

Ich kann nicht sagen, was in Ihrer Methode vor sich geht, wie die Struktur ist nicht klar, und es scheint, auf den Kontext der Anwendung abhängig.

Aus konstruktiver Sicht könnte es einfacher sein, eine spezielle Wrapper-Klasse zu erstellen, die all diese Funktionen kapselt. Stattdessen object.attribute_name zu nennen würden Sie object.dynamic_attributes.attribute_name nennen, wo in diesem Fall dynamic_attributes auf Anfrage erstellt:

def dynamic_attributes 
    @dynamic_attributes ||= DynamicAccessor.new(self) 
end 

Wenn das Objekt initialisiert wird, wird es vorkonfigurieren sich mit dem, was Methoden erforderlich sind, und Sie müssen nicht umgehen mit dieser Methode fehlende Sachen.

+0

danke für die Antwort, ich war nicht in der Lage, es funktioniert allein über den Code oben, aber ich habe einige davon in Refactoring, so akzeptierte ich als Antwort – thrice801

0

Für alle anderen gleiche Art der Sache zu tun versuchen, aber Probleme haben, die Probleme/Lösungen soweit ich waren Figur könnte aus:

1) Obwohl ich den folgenden Code, dachte funktionieren würde:

self.project_category_field_value.send("field_#{field.id}".to_sym, *args) 

Das würde jedes Mal eine neue Instanz des verwandten Modells zurückgeben, weshalb es verloren ging.

2) Sie müssen das zugehörige Objekt manuell speichern, da das zugehörige Modell nicht gespeichert wird. Ich habe am Ende ein Flag auf das Modell gesetzt und einen Rückruf hinzugefügt, um das zugehörige Modell zu speichern, wenn ein Flag existierte, z.

case(type) 

when :writer 
    self.update_dynamic_attributes=(true) 
    etc...... 

und dann Rückruf,

before_update :update_dynamic_attributes, :if => :update_dynamic_attributes? 

    def update_dynamic_attributes? 
    instance_variable_get("@update_dynamic_attributes") 
    end 

    def update_dynamic_attributes=(val) 
    instance_variable_set("@update_dynamic_attributes",val) 
    end 

    def update_dynamic_attributes 
    self.project_category_field_value.save 
    end 

3) Zurück zu # 1, war größte Problem neue Objektinstanz jedes Mal zurückgegeben wurde. Ich habe versucht, die Methode define_method zu verwenden, bevor ich diese Frage gestellt habe, aber es hat nicht funktioniert, und das war letztendlich das, was ich tun musste, um es zum Laufen zu bringen. fühlen Die Lösung hat mich ziemlich dumm, aber ich bin sicher, andere laufen in sie als gut, so stellen Sie sicher, dass, wenn Sie mit define_method direkt in der aktiven Datensatzklasse, Sie rufen

self.class.send(:define_method, name) 

statt

-
self.send(:define_method, name) 

oder Sie werden :(wenn es keine worky

3

Sie bei meiner Präsentation sehen kann, wo ich beschrieben, wie Methoden zum zugehörigen Modellen delegieren EAV with ActiveRecord

Zum Beispiel verwenden wir STI für unsere Produkt Modelle und wir haben Attribut Modelle für sie verbunden.

Zuerst erstellen wir die abstrakte Attribut Modell

class Attribute < ActiveRecord::Base 
    self.abstract_class = true 
    attr_accessible :name, :value 
    belongs_to :entity, polymorphic: true, touch: true, autosave: true 
end 

Dann werden alle unsere Attribut Modelle von dieser Klasse geerbt werden.

class IntegerAttribute < Attribute 
end 

class StringAttribute < Attribute 
end 

Jetzt müssen wir die Basis Produktklasse

class Product < ActiveRecord::Base 
    %w(string integer float boolean).each do |type| 
    has_many :"#{type}_attributes", as: :entity, autosave: true, dependent: :delete_all 
    end 

    def eav_attr_model(name, type) 
    attributes = send("#{type}_attributes") 
    attributes.detect { |attr| attr.name == name } || attributes.build(name: name) 
    end 

    def self.eav(name, type) 
    attr_accessor name 

    attribute_method_matchers.each do |matcher| 
     class_eval <<-EOS, __FILE__, __LINE__ + 1 
     def #{matcher.method_name(name)}(*args) 
      eav_attr_model('#{name}', '#{type}').send :#{matcher.method_name('value')}, *args 
     end 
     EOS 
    end 
    end 
end 

So fügten wir die #eav_attr_model Verfahren beschreiben, das ist ein Proxy Methode, um unsere zugehörigen Modelle und die .eav Methode, die Methoden Attribut erzeugt .

Es ist alles. Jetzt können wir unsere Produktmodelle erstellen, die von Produkt Klasse vererbt werden.

class SimpleProduct < Product 
    attr_accessible :name 

    eav :code, :string 
    eav :price, :float 
    eav :quantity, :integer 
    eav :active, :boolean 
end 

Verbrauch:

SimpleProduct.create(code: '#1', price: 2.75, quantity: 5, active: true) 
product = SimpleProduct.find(1) 
product.code  # "#1" 
product.price # 2.75 
product.quantity # 5 
product.active? # true 

product.price_changed? # false 
product.price = 3.50 
product.code_changed? # true 
product.code_was  # 2.75 

wenn Sie aa komplexere Lösung, die Attribute in Laufzeit oder verwenden Abfragemethoden, um Daten erstellen können Sie meine Perle aussehen kann hydra_attribute die die implementiert EAV für active_record-Modelle.

+0

Danke für die Freigabe. Ich versuche nur, ein EAV-Modell zu bekommen, und das war hilfreich. –