4

J'ai un modèle comme suit:Comment puis-je étendre un objet renvoyé à partir d'une association ActiveRecord lors de l'exécution?

class Property < ActiveRecord::Base 
    has_and_belongs_to_many :property_values 
end 

Ce que je voudrais faire est d'étendre toute valeur retournée par une découverte sur l'extension property_values ​​avec un module qui est déterminé par un attribut de l'objet de la propriété. Je l'ai essayé quelque chose comme ceci:

class Property < ActiveRecord::Base 
    has_and_belongs_to_many :property_values, :extend => PropertyUtil::Extensible 

    def enrich(to_extend) 
    modules.split(/\s*,\s*/).each do |mod| 
     to_extend.extend(Properties.const_get(mod.to_sym)) 
    end 
    end 
end 

module PropertyUtil 
    module Extensible 
    def self.extended(mod) 
     mod.module_eval do 
     alias old_find find 
     end 
    end 

    def find(*args) 
     old_find(*args).map{|prop| proxy_owner.enrich(prop)} 
    end 
    end 
end 

Lorsque tous les modules qui peuvent être sélectionnés sont définis dans le module Propriétés. En essayant de fonctionner avec ce code, cependant, il y a quelques problèmes; d'abord, à ma grande surprise, aucun des finders dynamiques (property_values.find_by_name, etc.) ne semble déléguer à find; Deuxièmement, quelque chose avec la façon dont j'ai fait l'aliasing conduit à un dépassement de pile lorsque j'essaie de lancer la recherche directement.

Existe-t-il un moyen de faire ce que je tente? Quelle méthode puis-je alias et redéfinir de sorte que tous les résultats retournés par l'extension d'association, indépendamment de la façon dont ils sont récupérés, sont étendus avec les modules appropriés?

Merci, Kris

+0

Il y a after_find et after_initialize callbacks que vous pouvez définir, mais il n'y a pas vraiment recommandé, car il provoque un grand succès de la performance. – jemminger

Répondre

0

J'ai fini par utiliser un appel after_find sur la classe de valeur pour résoudre ce problème. C'est une solution assez sous-optimale, car cela signifie que l'information du module finit par devoir être dupliquée entre le référent de la propriété et la valeur, mais c'est faisable, si ce n'est pas vraiment performant. Le résultat de la performance est devenu suffisamment important pour que je puisse mettre en cache un tas de données dans la base de données avec les résultats de calculs sur un grand nombre de propriétés, mais cela ne s'est pas avéré être tout à fait mauvais. signaler des données considérablement.

En fin de compte, voici quelques bribes de ce que j'ai fini avec:

module Properties::NamedModules 
    def modules 
    (module_names || '').split(/\s*,\s*/).map do |mod_name| 
     Property.const_get(mod_name.demodulize.to_sym) 
    end 
    end 
end 

module Properties::ModularProperty 
    def value_structure 
    modules.inject([]){|m, mod| m + mod.value_structure}.uniq 
    end 
end 

module Properties::Polymorphic 
    include NamedModules, ModularProperty 

    def morph 
    modules.each {|mod| self.extend(mod) unless self.kind_of?(mod)} 
    end 
end 

class Property < ActiveRecord::Base 
    include Properties::NamedModules, Properties::ModularProperty 

    has_and_belongs_to_many :property_values, :join_table => 'property_value_selection' 

    def create_value(name, value_data = {}) 
    property_values.create( 
     :name => name, 
     :module_names => module_names, 
     :value_str => JSON.generate(value_data) 
    ) 
    end 
end 

class PropertyValue < ActiveRecord::Base 
    include Properties::Polymorphic 
    has_and_belongs_to_many :properties, :join_table => 'property_value_selection' 
    after_find :morph 
end 
0

Je ne ai jamais essayé de le faire mais vous pouvez essayer ce qui suit (je viens de changer la façon dont les alias sont effectués):

class Property < ActiveRecord::Base 
    has_and_belongs_to_many :property_values, :extend => PropertyUtil::Extensible 

    def enrich(to_extend) 
    modules.split(/\s*,\s*/).each do |mod| 
     to_extend.extend(Properties.const_get(mod.to_sym)) 
    end 
    end 
end 

module PropertyUtil 
    module Extensible 
    def self.extended(mod) 
     mod.module_eval do 
     alias_method :old_find, :find 
     alias_method :find, :new_find 
     end 
    end 

    def new_find(*args) 
     old_find(*args).map{|prop| proxy_owner.enrich(prop)} 
    end 
    end 
end 

Si cela ne fonctionne pas ici est une autre idée vous voulez essayer:

class Value < ActiveRecord::Base 
    self.abstract_class = true 

end 


class ExtendedValue < Value 

end 

class ExtendedValue2 < Value 

end 


class Property < ActiveRecord::Base 
    has_and_belongs_to_many :property_values, :class_name => 'ExtendedValue' 
    has_and_belongs_to_many :property_values_extended, :class_name => 'ExtendedValue' 
    has_and_belongs_to_many :property_values_extended2, :class_name => 'ExtendedValue2' 


end 

l'idée est d'avoir une association hatbm par « type » (si vous pouvez regrouper vos extensions de cette façon) et utiliser celui que vous voulez à un moment donné, si vous le pouvez faire Ce que vous voulez de cette façon, je suis également assez sûr qu'il aura une performance d'impact plus petite que de patcher chaque objet retourné après activationecord les a renvoyés.

Je suis un peu curieux à ce que vous essayez d'atteindre avec ce :)

+0

Merci pour votre réponse!L'objectif est de fournir un type d'héritage à table unique plus flexible, où un certain nombre de modules différents peuvent composer pour fournir des sémantiques de comportement et de données pour les valeurs de propriété sans avoir à modifier la base de données pour accueillir de nouveaux types. Dans mon implémentation, il existe une seule propriété de texte, value_str, qui stocke un document JSON; Les différents modules, lorsqu'ils sont composés, définissent à la fois la structure du document et la façon de traiter ses données. Les utilisateurs peuvent mélanger et assortir les modules disponibles dans n'importe quelle combinaison désirée. –

+0

Avez-vous regardé composer_of? Cela peut aussi faire partie d'une solution à votre problème. – Schmurfy

+0

compose_of est proche, mais fait partie de la définition de classe et ne semble pas vous donner l'occasion de résoudre les noms de modules après que l'enregistrement a été extrait de la base de données. –

0

Il est beaucoup plus facile à utiliser simplement des cours pour changer la fonctionnalité. Vous pouvez avoir des classes de PropertyValues ​​avec le comportement approprié et utiliser STI (Single Table Inheritance) pour instancier l'instance appropriée ou vous pouvez remplacer la méthode de classe 'instantiate' ActiveRecord pour définir la classe en utilisant la méthode d'instance #becomes:

class PropertyValue < AR:Base 
    def self.instantiate(record) 
    property_value = super 
    case property_value.sub # criteria for sub_class 
     when 'type1' then property_value.becomes(Type1) 
     when 'type2' then property_value.becomes(Type2) 
    end 
    end 
end 

class Type1 < PropertyValue 
    def some_method 
    # do Type1 behavior 
    end 
end 

class Type2 < PropertyValue 
    def some_method 
    # do Type2 behavior 
    end 
end 

J'ai trouvé que l'utilisation de classes et d'héritage fournit un code beaucoup plus propre et plus simple et est plus facile à tester.

+0

Malheureusement, cela ne satisfait pas mon cas d'utilisation - j'ai 15 modules différents, dont n'importe quel nombre peut être composé pour spécifier le comportement et la structure de données d'une valeur de propriété. Il n'est pas judicieux de construire des classes pour le produit cartésien de ces combinaisons. –