Как расширить объект, возвращенный из ассоциации ActiveRecord во время выполнения? - PullRequest
4 голосов
/ 06 декабря 2010

У меня есть следующая модель:

class Property < ActiveRecord::Base
  has_and_belongs_to_many :property_values
end

Что я хотел бы сделать, это расширить любое значение, возвращаемое находкой в ​​расширении property_values, с помощью модуля, который определяется атрибутом Property.объект.Я пытался что-то вроде этого:

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 голосов
/ 04 апреля 2011

Гораздо проще просто использовать классы для изменения функциональности.Вы можете иметь классы PropertyValues ​​с соответствующим поведением и использовать либо STI (Single Table Inheritance) для создания экземпляра соответствующего экземпляра, либо вы можете переопределить метод класса «instantiate» ActiveRecord для установки класса, используя метод экземпляра #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

Я обнаружил, что использование классов и наследования обеспечивает намного более чистый и простой код и его легче тестировать.

0 голосов
/ 05 апреля 2011

Я решил использовать вызов 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
0 голосов
/ 23 марта 2011

Я никогда не пытался это сделать, но вы можете попробовать следующее (я просто изменил способ создания псевдонимов):

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 вернет их.

Мне любопытно, чего вы пытаетесь достичь с помощью этого:)

...