Ruby on Rails 中 HABTM 的 has_many :through

yufei       6 年, 3 月 前       1283

说句实话,当我第一次看到 「 HABTM 」 就在想,这到底是什么鬼 ?

百度谷歌了一下,哦,原来这个概念出自 Ruby on Rails 中。它的原本意思是 「 HasAndBelongsToMany 」,如果使用下划线,就是 「 has_and_belongs_to_many 」 ,但缩写成 「 HABTM 」 真的很让人匪夷所思。这是一个令人困过的首字母缩写,它的缩写和它的本意完全是毫无相关的。

但,我们有什么办法呢? 记住就是了...

我们今天并不讨论 HABTM ,有空我找个时间了解了解吧。我们今天讨论的是 「 has_many :through」 这个语句。

用例

为了正确的理解 「 has_many through 」,我们先做一个假设: 假如我们有个博客,它有两张表

  1. 一篇文章 ( Posts ) habtm 多个标签 ( Tags )
  2. 一个标签 ( Tags ) habtm 多篇文章 ( Posts)

复杂的要死,意思就是 「 文章和标签属于多对多的关系 」

这种关系在 Ruby on Rails 是如何表现的呢 ?

假设我们已经存在了两个模型 ( model ) TagPost,前者表示标签模型,后者是文章模型。

那么,这两个模型的表连接迁移 ( 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! 又可以用了

目前尚无回复
简单教程 = 简单教程,简单编程
简单教程 是一个关于技术和学习的地方
现在注册
已注册用户请 登入
关于   |   FAQ   |   我们的愿景   |   广告投放   |  博客

  简单教程,简单编程 - IT 入门首选站

Copyright © 2013-2022 简单教程 twle.cn All Rights Reserved.