【问题标题】:Bi-directional polymorphic join model in Rails?Rails中的双向多态连接模型?
【发布时间】:2010-08-17 14:21:16
【问题描述】:

我正在开发一个多站点 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,但不确定它是否受支持。

非常感谢任何帮助——即使它只是让我更加了解我对多态性在这种情况下的使用的不完全理解。

提前致谢!

【问题讨论】:

  • primary_articlessecondary_articlesoriginating_articles 应该是作用域而不是关联。

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


【解决方案1】:

如果您需要比 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

如果您需要它们更干燥,您可以将这些函数定义移到其他地方:

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

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

【讨论】:

  • 这是很棒的东西——谢谢!我当然会从这个线程中了解更多关于 ActiveRecord 的信息。我已将此标记为正确答案,因为它回到了多态性。 :-)
  • @trevrosen 我想我的关键点可以归结为:如果您无法访问插件来为您执行此操作,请自己编写 :) 这并不可怕。
  • 好点。在这里进行一个小的技术编辑,以防其他人想要使用其中的一些代码:Article 中的上述范围需要包装在 lambda 中才能正常工作。 :-)
【解决方案2】:

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

【讨论】:

  • 我已经查看了 has_many :through 多态,但我的问题更多的是让关联让我在其上放置那些额外的布尔属性而不是其他任何东西——这就是挂断的地方在;我正在制作一个“胖”连接模型。
  • 主要/次要问题实际上是每个站点的问题,因此它是连接模型中的问题。这似乎就是问题所在——拥有一个“胖”连接模型,它也是多态的。
【解决方案3】:

在这种情况下,我认为多态不是正确的方法,至少从我对您系统设计的理解来看。这是一个使用 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,您还需要添加它以及子模型使用的所有其他列。

【讨论】:

  • 这种方法看起来非常有用——感谢您传递它。我明白你关于通过连接使用范围的意思,我因为没有想到这一点而自责。我一直希望能够使用包含的模块或“acts_as”来为任何需要它们保持干燥的新内容类型创建关联。鉴于 STI 依赖于继承而不是 mixin,这可以在 STI 的上下文中完成吗?
  • @trevrosen 我进行关联的方式是,您必须为每种数据类型添加一个新列。因此,如果可扩展性非常重要,这可能不会很好地工作。但我认为多态性会使处理关联变得困难,因此您可能不得不滚动自己的助手来进行类型检查。
  • 我是否会选择 STI 还是继续使用 SiteConnectable 模块的策略,该模块依赖于多个连接模型 w/a类似的结构。目前,可扩展性似乎将我推向后者,但如果有意义的话,这种方法应该可以让我在以后轻松转换为 STI。无论哪种方式,你的答案都是有启发性的...... :-)
猜你喜欢
  • 1970-01-01
  • 2023-04-04
  • 1970-01-01
  • 2017-05-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-03-10
相关资源
最近更新 更多