设置多态 has_many :through 关系

Posted

技术标签:

【中文标题】设置多态 has_many :through 关系【英文标题】:Setting up a polymorphic has_many :through relationship 【发布时间】:2011-05-04 16:34:27 【问题描述】:
rails g model Article name:string
rails g model Category name:string
rails g model Tag name:string taggable_id:integer taggable_type:string category_id:integer

我已经创建了我的模型,如前面的代码所示。文章将是许多可以有标签的模型之一。类别模型将包含所有可以分配的类别。标记模型将是一个表示标记关系的多态连接表。

class Article < ActiveRecord::Base
  has_many :tags, :as => :taggable
  has_many :categories, :through => :taggable
end

class Category < ActiveRecord::Base
  has_many :tags, :as => :taggable
  has_many :articles, :through => :taggable
end

class Tag < ActiveRecord::Base
  belongs_to :taggable, :polymorphic => true
  belongs_to :category
end

我似乎无法让它工作,我可以做到非多态,但我一定是多态部分有问题。有什么想法吗?

编辑:仍然没有正确:

class Article < ActiveRecord::Base
    has_many :taggables, :as => :tag
    has_many :categories, :through => :taggables, :source => :tag, :source_type => "Article"
end
class Category < ActiveRecord::Base
    has_many :taggables, :as => :tag
    has_many :articles, :through => :taggables, :source => :tag, :source_type => "Article"
end
class Tag < ActiveRecord::Base
  belongs_to :taggable, :polymorphic => true
  belongs_to :category
end

【问题讨论】:

今天打算尝试一下,看看我是否完全理解如何做到这一点。 【参考方案1】:

要创建多态 has_many :through,您必须首先创建模型。我们将使用“Article”、“Category”和“Tag”,其中“Tag”是连接模型,Article 是可以用类别“标记”的众多对象之一。

首先,您创建“文章”和“类别”模型。这些是基本模型,暂时不需要特别注意:

rails g model Article name:string
rails g model Category name:string

现在,我们将创建多态连接表:

rails g model Tag taggable_id:integer taggable_type:string category_id:integer

连接表通过多态行为将两个表连接在一起,或者在我们的例子中是一个表与许多其他表。它通过从两个单独的表中存储 ID 来做到这一点。这将创建一个链接。我们的“Category”表将始终是“Category”,因此我们包含“category_id”。它链接到的表会有所不同,因此我们添加了一个项目“taggable_id”,它包含任何可标记项目的 id。然后,我们使用 'taggable_type' 完成链接,让链接知道它所链接的内容,例如文章。

现在,我们需要设置我们的模型:

class Article < ActiveRecord::Base
  has_many :tags, :as => :taggable, :dependent => :destroy
  has_many :categories, :through => :tags
end
class Category < ActiveRecord::Base
  has_many :tags, :dependent => :destroy
  has_many :articles, :through => :tags, :source => :taggable, :source_type => 'Article'
end
class Tag < ActiveRecord::Base
  belongs_to :taggable, :polymorphic => true
  belongs_to :category
end

在此之后,使用以下命令设置您的数据库:

rake db:migrate

就是这样!现在,您可以使用真实数据设置数据库:

Category.create :name => "Food"
Article.create :name => "Picking the right restaurant."
Article.create :name => "The perfect cherry pie!"
Article.create :name => "Foods to avoid when in a hurry!"
Category.create :name => "Kitchen"
Article.create :name => "The buyers guide to great refrigeration units."
Article.create :name => "The best stove for your money."
Category.create :name => "Beverages"
Article.create :name => "How to: Make your own soda."
Article.create :name => "How to: Fermenting fruit."

现在您有几个类别和各种文章。但是,它们没有使用标签进行分类。所以,我们需要这样做:

a = Tag.new
a.taggable = Article.find_by_name("Picking the right restaurant.")
a.category = Category.find_by_name("Food")
a.save

然后您可以对每个重复此操作,这将链接您的类别和文章。完成此操作后,您将能够访问每个文章的类别和每个类别的文章:

Article.first.categories
Category.first.articles

注意事项:

1) 每当您要删除由链接模型链接的项目时,请务必使用“销毁”。当您销毁链接对象时,它也会销毁链接。这样可以确保没有坏链接或死链接。这就是我们使用 ':dependent => :destroy' 的原因

2)在设置我们的“文章”模型时,它是我们的“可标记”模型之一,它必须使用 :as 链接。因为在前面的例子中我们使用了 'taggable_type' 和 'taggable_id' 我们使用 :as => :taggable。这有助于 Rails 知道如何将值存储在数据库中。

3) 将类别链接到文章时,我们使用: has_many :articles, :through => :tags, :source => :taggable, :source_type => 'Article' 这告诉类别模型它应该有许多 :articles 到 :tags。来源是 :taggable,原因同上。 source-type 是“Article”,因为模型会自动将 taggable_type 设置为自己的名称。

【讨论】:

您能否包括迁移中的最佳索引? 视图将如何工作?我可以让它渲染,但不能保存到数据库中。我试过了:&lt;%= f.collection_select :tags, Category.all, :id, :name, :prompt=&gt; 'Select up to 3', :class=&gt;'o-input--quiet' %&gt;【参考方案2】:

您根本无法使连接表具有多态性,至少 Rails 不支持这一点。解决方案是(取自 Obie's Rails 3 方式):

如果您真的需要它,has_many :through 可以使用多态关联,但只能通过准确指定您想要的多态关联类型。为此,您必须使用:source_type 选项。在大多数情况下,您必须使用:source 选项,因为关联名称与用于多态关联的接口名称不匹配:

class User < ActiveRecord::Base
  has_many :comments
  has_many :commented_timesheets, :through => :comments, :source => :commentable,
           :source_type => "Timesheet"
  has_many :commented_billable_weeks, :through => :comments, :source => :commentable,
           :source_type => "BillableWeek"

如果你走这条路,它很冗长,整个方案失去了它的优雅,但它确实有效:

User.first.commented_timesheets

希望我能帮上忙!

【讨论】:

以上是关于设置多态 has_many :through 关系的主要内容,如果未能解决你的问题,请参考以下文章

有关如何使用has_many:through关系正确设置验证的指导?

has_and_belongs_to_many 或多态 has_many :through?

Rails RSpec 测试 has_many :through 关系

何时在 Rails 中使用“has_many :through”关系?

如何避免 has_many :through 关系中的重复?

使用 has_many :through 和 build