How can I extend an object returned from an ActiveRecord association at runtime?

I have a model as follows:

class Property < ActiveRecord::Base has_and_belongs_to_many :property_values end 

What I would like to do is extend any value returned by a search in the property_values ​​extension with a module that is defined by the attribute of the Property object. I tried something like this:

 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 

Where all the modules that can be selected are defined in the Properties module. However, trying to run this code, there are several problems; firstly, to my surprise, none of the dynamic searchers (property_values.find_by_name, etc.) seem to delegate to find; secondly, something with the way I did the overlay causes the stack to overflow when I try to start the search directly.

Is there a way to do what I'm trying to do? Which method can I modify and override so that all the results returned by the association extension, regardless of how they are retrieved, are expanded using the appropriate modules?

Thank you, Chris

+4
source share
3 answers

I decided to use the after_find call of the value class to solve this problem. This is a fairly suboptimal solution, because it means that information about the module ends with the need for duplication between the referent of the properties and the value, but it is workable if it is less accurate. The performance acceleration was so great that I had to cache a bunch of data in the database with the results of calculations for a large number of properties, but this turned out to be not entirely bad, since it simplified the process of extracting reporting data significantly.

In the end, here are a few bits of what I got:

 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
source

I never tried to do this, but you can try the following (I just changed the way aliases are executed):

 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 

If this does not work, this is another idea you can try:

 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 

The idea is to have one hat association per type (if you can group your extensions this way) and use the one you want at a given time, if you can do what you want, I'm sure it will have less impact performance than fixing each returned object after activerecord returns them.

I'm curious what you are trying to achieve with this :)

0
source

It's much easier to just use classes to change functionality. You can have PropertyValues ​​classes with the appropriate behavior and use either STI (Single Table Inheritance) to instantiate the corresponding instance, or you can override the ActiveRecord instance class method to create the class using the #becomes instance method:

 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 

I found that using classes and inheritance provides much cleaner, simpler code and is easier to test.

0
source

All Articles