说句实话,当我第一次看到 「 HABTM 」 就在想,这到底是什么鬼 ?
百度谷歌了一下,哦,原来这个概念出自 Ruby on Rails 中。它的原本意思是 「 HasAndBelongsToMany 」,如果使用下划线,就是 「 has_and_belongs_to_many 」 ,但缩写成 「 HABTM 」 真的很让人匪夷所思。这是一个令人困过的首字母缩写,它的缩写和它的本意完全是毫无相关的。
但,我们有什么办法呢? 记住就是了...
我们今天并不讨论 HABTM ,有空我找个时间了解了解吧。我们今天讨论的是 「 has_many :through」 这个语句。
用例
为了正确的理解 「 has_many through 」,我们先做一个假设: 假如我们有个博客,它有两张表
- 一篇文章 ( Posts )
habtm
多个标签 ( Tags ) - 一个标签 ( Tags )
habtm
多篇文章 ( Posts)
复杂的要死,意思就是 「 文章和标签属于多对多的关系 」
这种关系在 Ruby on Rails 是如何表现的呢 ?
假设我们已经存在了两个模型 ( model ) Tag
和 Post
,前者表示标签模型,后者是文章模型。
那么,这两个模型的表连接迁移 ( migration ) 一般如下
class CreatePostsTags < ActiveRecord::Migration[5.1] def change create_table :posts_tags, id: false do |t| t.belongs_to :post, index: true t.belongs_to :tag, index: true end end end
然后还需要在各自的模型中定义和对方的关系
class Post < ApplicationRecord has_and_belongs_to_many :tags end class Tag < ApplicationRecord has_and_belongs_to_many :posts end
BingGo. ! 能正常工作
程序员的悲剧来了,可能过了几个月之后,为了实现搜索目的,可能需要对帖子的标签进行排序。
怎么办? 怎么办?
我们就不讲那些不过脑经的想法了,直接上一个最酷的东西,就是使用 「 HABTM 」
HABTM 中的 has_many :through
为什么是它?
has_many :through
语句可以将一个表连接 ( join ) 直接声明为一个模型,然后就可以对这个模型做一些表连接的操作,例如排序 ( order ) ,过滤 ( filter ),允许 ( allow ) 、拒绝 ( reject ) 。
在这个表连接模型中,还可以直接将标签的 rank
数据存储在帖子的标签列表中
迁移问题 ?
class CreatePostsTags < ActiveRecord::Migration[5.1] def change create_table :posts_tags, id: false do |t| t.belongs_to :post, index: true t.belongs_to :tag, index: true end end end
每个模型都需要提供一个主键 ( primary key ) 用于检索目的,默认的,Ruby on Rails 中使用 id
字段作为主键。但在我们的表连接模型中,这个 id 主键会出一些问题,主要还是我们在表连接模型中删除了 id 列 ( id: false )。
删除 id 键是合理的,因为我们只需要 tag_id 和 post_id 两列就能获取一条记录。
但为了使用 has_many :through
语句,我们又必须在 posts_tags
表中添加一个主键。
那怎么办呢 ?
答案呢,很简单,就是 重命名 表,然后在新表中添加 id
列并添加主键属性。
class AddPrimaryKeyAndRankToPostsTags < ActiveRecord::Migration[5.1] def change rename_table 'posts_tags', 'post_tags' add_column :post_tags, :id, :primary_key add_column :post_tags, :rank, :integer, default: 0 end end
然后呢,在各自的模型中还需要做一些小修改
class PostTag < ApplicationRecord belongs_to :post belongs_to :tag end class Post < ApplicationRecord has_many :post_tags, -> { order(rank: :asc) } has_many :tags, through: :post_tags end class Tag < ApplicationRecord has_many :post_tags has_many :posts, through: :post_tags end
BingGo! 又可以用了