Rails中的双向多态连接模型?

时间:2010-08-17 14:21:17

标签: ruby-on-rails activerecord ruby-on-rails-3 polymorphic-associations

我正在开发一个多站点CMS,它具有站点间交叉发布的概念。几种类型的内容(文章,事件,Bios等)可以与许多网站相关联,而网站可以包含许多内容。内容片段和站点之间的多对多关联还必须支持每个相关内容项的一些共同属性 - 站点起源的概念(这是内容出现的原始站点吗?)以及概念给定关联网站上给定内容的“主要”和“次要”内容状态。

我的想法是创建一个名为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 个答案:

答案 0 :(得分: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移到别处:

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

现在它开始变得更像通用插件,而不是特定于应用程序的代码。这很好,因为你可以轻松地重复使用它。

答案 1 :(得分:1)

只是一个镜头,但是你看过多态的has_many:through =&gt;关系?有一些有用的博客文章 - 尝试http://blog.hasmanythrough.com/2006/4/3/polymorphic-throughhttp://www.inter-sections.net/2007/09/25/polymorphic-has_many-through-join-model/(还有一个问题here)。希望其中一些有点帮助,祝你好运!

答案 2 :(得分: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,您还需要添加该项以及子模型使用的每个其他列。