3

我正在研究一個多站點CMS,它在站點之間具有交叉發佈的概念。多種類型的內容(文章,活動,BIOS等)可以與許多網站相關聯,並且網站可以包含許多內容。內容片段和網站之間的多對多關聯還必須支持關聯的每個內容項目的幾個共同屬性 - 網站創建的概念(這是內容出現的原始網站?)以及概念給定關聯網站上給定內容的「主要」和「次要」內容狀態。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'和'originating_articles'應該是範圍而不是關聯。 – 2010-08-17 23:03:48

回答

9

如果您需要關聯比STI允許的更具擴展性,您可以嘗試編寫自己的集合助手來進行額外的類型自檢。

你定義一個belongs_tohas_manyhas_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 

而且,由於文章,事件& BIOS在這個例子中都做同樣的事情,我們可以幹這甚至更多:

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 2010-08-24 13:34:15

+0

@trevrosen我想我的觀點的癥結歸結爲:如果你沒有訪問插件來爲你做這件事,請自己寫:)這不是那麼可怕。 – 2010-08-24 18:07:45

+0

好點。如果其他人希望使用這些代碼,可以在這裏進行一些小的技術編輯:本文上面的範圍需要用lambda包裝才能正常工作。 :-) – trevrosen 2010-08-25 16:04:58

1

只是一個鏡頭,但你看看多態has_many:通過=>關係?有幾個有用的博客文章 - 嘗試http://blog.hasmanythrough.com/2006/4/3/polymorphic-throughhttp://www.inter-sections.net/2007/09/25/polymorphic-has_many-through-join-model/(也有一個問題here)。希望其中的一些有所幫助,祝你好運!

+0

我查看了has_many:通過多態性,但我的問題更多的是讓關聯讓我把這些額外的布爾屬性比它是其他任何東西 - 這就是掛斷進來的地方;我正在製作一個「胖」連接模型。 – trevrosen 2010-08-17 19:45:50

+0

嗯,是的 - @亞當建立示波器的建議聽起來很有前途...... – Budgie 2010-08-18 14:22:07

+0

主要/次要的事情實際上是一個每站點問題,所以它是在連接模型上發生的事情。這就是問題的癥結所在 - 擁有一個「胖」的連接模型也是多態的。 – trevrosen 2010-08-18 17:56:01

1

在這種情況下,我不認爲多態是正確的選擇,至少從我對系統設計的理解來看。這是一個使用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,您還需要添加該子模型以及子模型使用的每個其他列。

+0

這種方法看起來非常有用 - 感謝傳遞它。我明白了你通過連接使用範圍的意思,而我正在踢自己沒有想到這一點。我一直希望能夠使用包含的模塊或「acts_as」爲任何需要它們的新內容類型創建關聯以保持乾燥。鑑於它依賴於繼承而不是混合,STI可以這樣做嗎? – trevrosen 2010-08-20 14:21:14

+0

@trevrosen我正在做關聯的方式,你必須爲每個數據類型添加一個新的列。所以如果可擴展性非常重要,這可能不會很好。但我認爲多態性會使關聯難以處理,所以你可能不得不推出你自己的助手來進行類型檢查。 – 2010-08-20 17:00:00

+0

對於我來說,對於我是否要與STI合作,或者如果我要繼續採用基於多個連接模型(具有類似結構)的SiteConnectable模塊的策略,我仍然處於空中。就目前而言,可擴展性似乎將我推向後者,但如果這種方法有意義,那麼這種方法應該可以讓我輕鬆地轉換爲STI。無論哪種方式,你的是一個啓發性的答案... :-) – trevrosen 2010-08-23 17:46:30