Двунаправленная модель полиморфного соединения в Rails? - PullRequest
3 голосов
/ 17 августа 2010

Я работаю над мультисайтовой CMS, которая имеет понятие перекрестной публикации среди сайтов.Несколько типов контента (Статьи, События, Биографии и т. Д.) Могут быть связаны со многими Сайтами, и Сайты могут иметь много частей контента.Ассоциация «многие ко многим» между частями контента и сайтами также должна поддерживать пару общих атрибутов для каждого связанного элемента контента - понятие происхождения сайта (это исходный сайт, на котором появился контент?), А также понятие«первичное» и «вторичное» состояние содержимого для заданной части содержимого на данном связанном сайте.

Моя идея заключалась в создании модели полиморфного объединения под названием ContentAssociation, но у меня возникают проблемы с получением полиморфных ассоциацийвести себя так, как я от них ожидаю, и мне интересно, возможно, я все об этом говорю неправильно.

Вот мои настройки для таблицы соединений и моделей:

create_table "content_associations", :force => true do |t|
  t.string   "associable_type"
  t.integer  "associable_id"
  t.integer  "site_id"
  t.boolean  "primary_eligible"
  t.boolean  "secondary_eligible"
  t.boolean  "originating_site"
  t.datetime "created_at"
  t.datetime "updated_at"
end

class ContentAssociation < ActiveRecord::Base
  belongs_to :site
  belongs_to :associable, :polymorphic => true
  belongs_to :primary_site, :class_name => "Site", :foreign_key => "site_id" 
  belongs_to :secondary_site, :class_name => "Site", :foreign_key => "site_id"
  belongs_to :originating_site, :class_name => "Site", :foreign_key => "site_id"
end

class Site < ActiveRecord::Base
  has_many :content_associations, :dependent => :destroy 
  has_many :articles, :through => :content_associations, :source => :associable, :source_type => "Article"
  has_many :events, :through => :content_associations, :source => :associable, :source_type => "Event"

  has_many :primary_articles, :through => :content_associations, 
                              :source => :associable, 
                              :source_type => "Article", 
                              :conditions => ["content_associations.primary_eligible = ?" true]

  has_many :originating_articles, :through => :content_associations, 
                                  :source => :associable, 
                                  :source_type => "Article", 
                                  :conditions => ["content_associations.originating_site = ?" true]

  has_many :secondary_articles, :through => :content_associations, 
                                :source => :associable, 
                                :source_type => "Article", 
                                :conditions => ["content_associations.secondary_eligible = ?" true]
end

class Article < ActiveRecord::Base
  has_many :content_associations, :as => :associable, :dependent => :destroy
  has_one :originating_site, :through => :content_associations, 
                             :source => :associable, 
                             :conditions => ["content_associations.originating_site = ?" true]

  has_many :primary_sites, :through => :content_associations, 
                           :source => :associable
                           :conditions => ["content_associations.primary_eligible = ?" true]

  has_many :secondary_sites, :through => :content_associations, 
                             :source => :associable
                             :conditions => ["content_associations.secondary_eligible = ?" true]                         
end

Я пробовал много вариантов вышеуказанных объявлений ассоциации, но независимо от того, что я делаю, я могу 'Кажется, я получаю поведение, которое мне нужно

@site = Site.find(2)
@article = Article.find(23)
@article.originating_site = @site
@site.originating_articles #=>[@article]

или этот

@site.primary_articles << @article
@article.primary_sites #=> [@site]

Является ли встроенный в Rails полиморфизм неправильным механизмом, который используется для воздействия на эти связи между сайтами и их различными частямисодержания?Кажется, это было бы полезно из-за того факта, что мне нужно соединить несколько разных моделей с одной общей моделью множеством способов, но мне было трудно найти какие-либо примеры, использующие ее таким образом.

Возможно, часть сложности заключается в том, что мне нужна ассоциация в обоих направлениях - т.е. чтобы увидеть все сайты, с которыми связана данная статья, и см. Все статьи, связанные сданный сайт.Я слышал о плагине has_many_polymorphs , и похоже, что он может решить мои проблемы.Но я пытаюсь использовать Rails 3 здесь и не уверен, что он поддерживается.

Любая помощь очень ценится - даже если она только проливает больше света на мое несовершенное понимание использования полиморфизма в этом контексте..

заранее спасибо!

Ответы [ 3 ]

9 голосов
/ 24 августа 2010

Если вам нужно, чтобы ассоциации были более расширяемыми, чем это допускает STI, вы можете попробовать написать собственных помощников по сбору, которые выполняют дополнительный тип-самоанализ.

Каждый раз, когда вы определяете связь с belongs_to, has_many или has_one и т. Д. Вы также можете определить вспомогательные функции, связанные с этой коллекцией:

class Article < ActiveRecord::Base
  has_many :associations, :as => :associable, :dependent => :destroy
  has_many :sites, :through => :article_associations

  scope :originating_site, lambda { joins(:article_associations).where('content_associations.originating_site' => true).first }
  scope :primary_sites, lambda { joins(:article_associations).where('content_associations.primary_eligable' => true) }
  scope :secondary_sites, lambda { joins(:article_associations).where('content_associations.secondary_eligable' => true) }
end

class Site < ActiveRecord::Base
  has_many :content_associations, :as => :associable, :dependent => :destroy do
    def articles
      collect(&:associable).collect { |a| a.is_a? Article }
    end
  end
end

class ContentAssociation < ActiveRecord::Base
  belongs_to :site
  belongs_to :associable, :polymorphic => true
  belongs_to :primary_site, :class_name => "Site", :foreign_key => "site_id"
  belongs_to :secondary_site, :class_name => "Site", :foreign_key => "site_id"
  belongs_to :originating_site, :class_name => "Site", :foreign_key => "site_id"
end

Вы можете переместить эти определения функций в другое место, если вам нужно, чтобы они были более сухими:

module Content
  class Procs
    cattr_accessor :associations
    @@associations = lambda do
      def articles
        collect(&:associable).collect { |a| a.is_a? Article }
      end

      def events
        collect(&:associable).collect { |e| e.is_a? Event }
      end

      def bios
        collect(&:associable).collect { |b| b.is_a? Bio }
      end
    end
  end
end


class Site < ActiveRecord::Base
  has_many :content_associations, :as => :associable, :dependent => :destroy, &Content::Procs.associations
end

И поскольку статьи, события и биографии в этом примере все делают одно и то же, мы можем СУШИТЬ это еще больше:

module Content
  class Procs
    cattr_accessor :associations
    @@associations = lambda do
      %w(articles events bios).each do |type_name|
        type = eval type_name.singularize.classify
        define_method type_name do
          collect(&:associable).collect { |a| a.is_a? type }
        end
      end
    end
  end
end

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

1 голос
/ 18 августа 2010

В этом случае я не думаю, что полиморфизм - это правильный путь, по крайней мере из того, что я понимаю в дизайне вашей системы.Вот пример использования STI.Это сложно, так что прости меня, если я что-то упустил.Я также не очень силен в новом синтаксисе arel, поэтому не могу гарантировать, что он будет функционировать без поворотов.

class Article < ActiveRecord::Base
  has_many :article_associations, :dependent => :destroy
  has_many :sites, :through => :article_associations

  scope :originating_site, lambda { joins(:article_associations).where('content_associations.originating_site' => true).first }
  scope :primary_sites, lambda { joins(:article_associations).where('content_associations.primary_eligable' => true) }
  scope :secondary_sites, lambda { joins(:article_associations).where('content_associations.secondary_eligable' => true) }
end

class Site < ActiveRecord::Base
  has_many :content_associations, :dependent => :destroy
  has_many :article_associations
  has_many :articles, :through => :article_associations
end

class ContentAssociation < ActiveRecord::Base
  belongs_to :site
  belongs_to :primary_site, :class_name => "Site", :foreign_key => "site_id"
  belongs_to :secondary_site, :class_name => "Site", :foreign_key => "site_id"
  belongs_to :originating_site, :class_name => "Site", :foreign_key => "site_id"
end

class ArticleAssociation < ContentAssociation
  belongs_to :article
end

То, что я делаю здесь, это создание модели базовой ассоциации и отдельной дочерней ассоциациидля каждого типа данных.Итак, если вам нужен доступ к ассоциациям по типу, у вас будет доступ к site.articles, но вы также можете получить список site.content_assocations со всем вместе.

Для функции STI потребуется столбец type:stringхранить тип данных.Это будет выполнено автоматически, если вы не используете модель ContentAssociation.Поскольку ArticleAssociation использует article_id, вам также необходимо добавить это и все остальные столбцы, которые используют дочерние модели.

1 голос
/ 17 августа 2010

Просто выстрел, но вы смотрели на полиморфные отношения has_many: through =>? Есть несколько полезных постов в блоге - попробуйте http://blog.hasmanythrough.com/2006/4/3/polymorphic-through и http://www.inter -sections.net / 2007/09/25 / polymorphic-has_many-through-join-model / (там было также вопрос здесь ). Надеюсь, что это поможет, удачи!

...