3

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

Моя идея состояла в том, чтобы создать модель полиморфного объединения, называемую 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 здесь и не уверен, что он еще поддерживается.

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

благодарит заранее!

+0

'primary_articles',' secondary_articles' и 'origination_articles' должны быть областями вместо ассоциаций. –

ответ

9

Если вам нужно, чтобы ассоциации были более расширяемыми, чем позволяло использовать 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 

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

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 

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

+0

Это замечательный материал - спасибо! Я определенно узнаю больше об ActiveRecord из этой темы. Я отметил это как правильный ответ, так как он возвращается полный полиморфизм. :-) – trevrosen

+0

@trevrosen Я предполагаю, что суть моей точки сводится к: если у вас нет доступа к плагину, чтобы сделать это за вас, напишите свой собственный :) Это не так страшно. –

+0

Хорошая точка. Один незначительный технический править здесь, если другие приходят искать какой-то из этого кода: области, указанные в статье, должны быть завернуты в лямбда для правильной работы. :-) – trevrosen

1

Просто выстрел, но вы посмотрели на полиморфные 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/ (был также вопрос here). Надеюсь, что некоторые из них немного помогают, удачи!

+0

Я просмотрел has_many: через полиморфизм, но моя проблема связана с тем, чтобы ассоциация позволяла мне помещать на нее эти дополнительные логические атрибуты, чем что-либо еще, - вот где происходит зависание; Я делаю «жирную» модель объединения. – trevrosen

+0

Хммм, да - @ Адам предлагает создать области звуков многообещающих ... – Budgie

+0

Первичная/вторичная вещь - это проблема на каждом сайте, поэтому это что-то, что происходит в модели соединения. Вот где проблема, похоже, - наличие модели «жирного» соединения, которая также является полиморфной. – trevrosen

1

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

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, вам также необходимо добавить это и каждый другой столбец, который используют дочерние модели.

+0

Этот подход очень полезен - спасибо за то, что он прошел его. Я понимаю, что вы подразумеваете при использовании областей через объединения, и я пинаю себя за то, что не думал об этом уже. Я надеялся, что смогу использовать подключенный модуль или «act_as» для создания ассоциаций для любого нового типа контента, который им нужен, чтобы сохранить вещи DRY. Может ли это быть сделано в контексте ИППП, учитывая, что оно зависит от наследования, а не от микширования? – trevrosen

+0

@trevrosen Как я делаю ассоциации, вам нужно будет добавить новый столбец для каждого типа данных. Поэтому, если расширяемость очень важна, это, вероятно, не сработает. Но я думаю, что полиморфизм затруднит общение с ассоциациями, поэтому вам, возможно, придется перевернуть ваших собственных помощников для проверки типов. –

+0

Мне все еще нравится, собираюсь ли я пойти с STI, или если я продолжу стратегию модуля SiteConnectable, опираясь на несколько моделей соединений с аналогичной структурой. На данный момент расширяемость, похоже, подталкивает меня к последней, но подход должен облегчить мне преобразование в STI позже, если это имеет смысл. В любом случае, ваш был поучительным ответом ... :-) – trevrosen

Смежные вопросы