如何在运行时扩展从ActiveRecord关联返回的对象?

时间:2010-12-06 06:58:33

标签: ruby-on-rails ruby ruby-on-rails-3 activerecord metaprogramming

我的模型如下:

class Property < ActiveRecord::Base
  has_and_belongs_to_many :property_values
end

我想要做的是使用由Property对象的属性确定的模块扩展property_values扩展上的find返回的任何值。我试过这样的事情:

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

可以选择的所有模块都在“属性”模块中定义。但是,尝试使用此代码运行时,存在一些问题;首先,令我惊讶的是,没有动态查找器(property_values.find_by_name等)似乎委托查找;第二,当我尝试直接运行查找时,我如何完成别名会导致堆栈溢出。

有没有办法做我正在尝试的事情?我可以使用什么方法进行别名和覆盖,以便关联扩展返回的所有结果,无论它们如何被检索,都会使用适当的模块进行扩展?

谢谢,克里斯

3 个答案:

答案 0 :(得分:0)

我从未尝试过这样做,但您可能想尝试以下操作(我只是更改了别名的完成方式):

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

如果它不起作用,你可能想尝试另一种想法:

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

这个想法是每个“类型”有一个hatbm关联(如果你可以按照这种方式对扩展进行分组)并在给定时间使用你想要的那个,如果你可以按照你想要的那样做,我也很确定在activerecord返回后,它将比修补每个返回的对象具有更小的影响性能。

我对你想要达到的目标有点好奇:)

答案 1 :(得分:0)

简单地使用类来更改功能要容易得多。您可以使用适当的行为来使用PropertyValues类,并使用STI(单表继承)来实例化相应的实例,或者您可以使用#becomes实例方法覆盖'实例化'ActiveRecord类方法来设置类:

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

我发现使用类和继承提供了更清晰,更简单的代码,并且更容易测试。

答案 2 :(得分:0)

我最终在值类上使用了after_find调用来解决此问题。这是一个非常不理想的解决方案,因为它意味着模块信息最终需要在属性引用和值之间重复,但是如果不是完全高效的话,它是可行的。性能影响最终变得足够大,以至于我不得不在数据库中缓存大量数据以及大量属性的计算结果,但事实证明这并不是一件坏事,因为它简化了提取的过程。大大报告数据。

最后,以下是我最终的一些内容:

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