как эмулировать has_many: через полиморфные классы - PullRequest
1 голос
/ 17 августа 2011

Я понимаю, почему ActiveRecord не может поддерживать has_many :through в полиморфных классах.Но я хотел бы подражать некоторым его функциональным возможностям.Рассмотрим следующее, где таблица объединения ассоциирует два полиморфных класса:

class HostPest < ActiveRecord::Base
  belongs_to :host, :polymorphic => true
  belongs_to :pest, :polymorphic => true
end
class Host < ActiveRecord::Base
  self.abstract_class = true  
  has_many :host_pests, :as => :host
end
class Pest < ActiveRecord::Base
  self.abstract_class = true  
  has_one :host_pest, :as => :pest
end
class Dog < Host ; end
class Cat < Host ; end
class Flea < Pest ; end
class Tick < Pest ; end

Цель

Поскольку я не могу сделать has_many :pests, :through=>:host_pests, :as=>:host (и т. Д.), Я бы хотел подражать этимчетыре метода:

dog.pests (returns a list of pests associated with this dog)
flea.host (return the host associated with this flea)
cat.pests << Tick.create (creates a HostPest record)
tick.host = Cat.create (creates a HostPest record)

Вопрос 1

У меня есть рабочая реализация для первых двух методов (pests и host), но я хочу знать, является ли этолучший способ (в частности, я пропускаю что-то в ассоциациях ActiveRecord, которое могло бы помочь):

class Host < ActiveRecord::Base
  def pests
    HostPest.where(:host_id => self.id, :host_type => self.class).map {|hp| hp.pest}
  end
end
class Pest < ActiveRecord::Base
  def host
    HostPest.where(:pest_id => self.id, :pest_type => self.class).first.host
  end
end

Вопрос 2

Я в тупике от того, как реализовать << и =Здесь подразумеваются методы:

cat.pests << Tick.create  # => HostPest(:host=>cat, :pest=>tick).create
tick.host = Cat.create    # => HostPest(:host=>cat, :pest=>tick).create

Есть предложения?(И снова, могут ли ассоциации ActiveRecord оказать какую-либо помощь?)

Ответы [ 2 ]

2 голосов
/ 18 августа 2011

Реализация метода host= в классе Pest проста. Нам нужно убедиться, что мы очищаем старый хост при настройке нового хоста (так как AR не удаляет старое значение из промежуточной таблицы.).

class Pest < ActiveRecord::Base
  self.abstract_class = true  
  has_one :host_pest, :as => :pest

  def host=(host)
    Pest.transaction do
      host_pest.try(:destroy) # destroy the current setting if any
      create_host_pest(:host => host)
    end
  end
end

Реализация метода pests<< в классе Host немного сложнее. Добавьте метод pests в класс Host, чтобы получить агрегированный список вредных организмов. Добавьте метод << к объекту, возвращенному методом pests.

class Host < ActiveRecord::Base
  self.abstract_class = true  
  has_many :host_pests, :as => :host

  # pest list accessor
  def pests
    @pests ||= begin
      host = self # variable to hold the current self. 
                  # We need it later in the block
      list = pest_list
      # declare << method on the pests list
      list.singleton_class.send(:define_method, "<<") do |pest|
        # host variable accessible in the block 
        host.host_pests.create(:pest => pest)
      end
      list
    end        
  end

private
  def pest_list
    # put your pest concatenation code here
  end
end

Теперь

cat.pests # returns a list
cat.pests << flea # appends the flea to the pest list
2 голосов
/ 18 августа 2011

Вы можете решить свою проблему с помощью ИППП и регулярной ассоциации:

class HostPest < ActiveRecord::Base
  belongs_to :host
  belongs_to :pest
end

Сохраните все хосты в таблице с именем hosts. Добавьте в таблицу строковый столбец с именем type.

class Host < ActiveRecord::Base
  has_many :host_pests
  has_many :pests, :through => :host_pests
end

Унаследуйте класс Host для создания новых хостов.

class Dog < Host ; end
class Cat < Host ; end

Храните все вредные организмы в таблице под названием pests. Добавьте в таблицу строковый столбец с именем type.

class Pest < ActiveRecord::Base
  has_one :host_pest
  has_one :host, :through => :host_pest
end

Унаследуйте класс Pest для создания новых вредных организмов.

class Flea < Pest ; end
class Tick < Pest ; end

Теперь, когда вы можете запустить следующие команды:

dog.pests (returns a list of pests associated with this dog)
flea.host (return the host associated with this flea)
cat.pests << Tick.create (creates a HostPest record)
tick.host = Cat.create (creates a HostPest record)

Примечание

Rails поддерживает has_many :through на полиморфных классах. Вам нужно указать source_type, чтобы это работало.

Рассмотрим модели для пометки:

class Tag
  has_many :tag_links
end

class TagLink
  belongs_to :tag
  belongs_to :tagger, :polymorphic => true
end

Допустим, товары и компании могут быть помечены.

class Product
  has_many :tag_links, :as => :tagger
  has_many :tags, :through => :tag_links
end

class Company
  has_many :tag_links, :as => :tagger
  has_many :tags, :through => :tag_links
end

Мы можем добавить ассоциацию к модели Tag, чтобы получить все помеченные продукты следующим образом:

class Tag
  has_many :tag_links
  has_many :products, :through => :tag_links, 
                        :source => :tagger, :source_type => 'Product'
end
...