1 关联是什么?
关联在两个 Active Record 模型之间建立联系
2 关联的类型
Rails 支持六种关联:
2.1 belongs_to
2.1.1 概述
belongs_to 关联创建两个模型之间一对一的关系,声明所在的模型实例属于另一个模型的实例。例如:图书和作者
class Book < ApplicationRecord
belongs_to :author
end
对下述声明来说,Book 模型的每个实例都获得了这些方法:
author
author=
build_author
create_author
create_author!
2.1.2 belongs_to 支持的选项
- :autosave:把 :autosave 选项设为 true,保存父对象时,会自动保存所有子对象,并把标记为析构的子对象销毁。
- :class_name:如果另一个模型无法从关联的名称获取,可以使用 :class_name 选项指定模型名。
- :counter_cache: 提高统计所属对象数量操作的效率,果想知道 @author.books.size 的结果,要在数据库中执行 COUNT(*) 查询。如果不想执行这个查询,可以在声明 belongs_to 关联的模型中加入计数缓存功能:
belongs_to :author, counter_cache: true
belongs_to :author, counter_cache: :count_of_books
-
:dependent
:dependent 选项控制属主销毁后怎么处理关联的对象: -
:foreign_key
foreign_key 选项可以设置要使用的外键名: -
:primary_key
按照约定,Rails 假定使用表中的 id 列保存主键。使用 :primary_key 选项可以指定使用其他列。 -
:inverse_of
:inverse_of 选项指定 belongs_to 关联另一端的 has_many 和 has_one 关联名。不能和 :polymorphic 选项一起使用。 -
:polymorphic
:polymorphic 选项为 true 时,表明这是个多态关联 -
:touch
如果把 :touch 选项设为 true,保存或销毁对象时,关联对象的 updated_at 或 updated_on 字段会自动设为当前时间。还可指定要更新哪个时间戳字段: -
:validate
如果把 :validate 选项设为 true,保存对象时,会同时验证关联的对象。该选项的默认值是 false,保存对象时不验证关联的对象。 -
:optional
如果把 :optional 选项设为 true,不会验证关联的对象是否存在。该选项的默认值是 false。
2.1.3 belongs_to 的作用域
有时可能需要定制 belongs_to 关联使用的查询,定制的查询可在作用域代码块中指定。
- where 方法指定关联对象必须满足的条件。
- includes 方法指定使用关联时要及早加载的间接关联。
- 如果使用 readonly,通过关联获取的对象是只读的。
- select 方法用于覆盖检索关联对象使用的 SQL SELECT 子句。默认情况下,Rails 检索所有字段。
2.2 has_one
has_one 关联也建立两个模型之间的一对一关系,表示模型的实例包含或拥有另一个模型的实例。例如,应用中每个供应商只有一个账户
class Supplier < ApplicationRecord
has_one :account
end
和belongs_to类似
2.3 has_many
2.3.1 概述
- has_many 关联建立两个模型之间的一对多关系。
- 在 belongs_to 关联的另一端经常会使用这个关联。has_many 关联表示模型的实例有零个或多个另一模型的实例。例如,作者和图书。
class Author < ApplicationRecord
has_many :books
end
声明 has_many 关联时,另一个模型使用复数形式。
2.3.2 has_many 关联添加的方法
- @author.books :返回一个数组,包含所有关联的对象。
- @author.books << @book1 :<< 方法向关联对象数组中添加一个或多个对象,并把各个所加对象的外键设为调用此方法的模型的主键。
- @author.books.delete(@book1) :从关联对象数组中删除一个或多个对象,并把删除的对象外键设为 NULL。
- @author.books.destroy(@book1) :从关联对象数组中删除一个或多个对象。
- @author.book_ids :singular_ids 方法返回一个数组,包含关联对象数组中各对象的 ID。
- has_many :articles, -> { distinct }, through: :readings
以确保集合中没有重复的对象。与 :through 选项一起使用最有用。
…(更多)
2.4 has_many :through
- has_many :through 关联经常用于建立两个模型之间的多对多关联。
- 这种关联表示一个模型的实例可以借由第三个模型,拥有零个和多个另一模型的实例。eg:病人要和医生约定练习时间。
class Physician < ApplicationRecord
has_many :appointments
has_many :patients, through: :appointments
end
class Appointment < ApplicationRecord
belongs_to :physician
belongs_to :patient
end
class Patient < ApplicationRecord
has_many :appointments
has_many :physicians, through: :appointments
end
2.5 has_one :through
- has_one :through 关联建立两个模型之间的一对一关系。
- 这种关联表示一个模型通过第三个模型拥有另一模型的实例。例如,每个供应商只有一个账户,而且每个账户都有一个账户历史,那么可以这么定义模型:
class Supplier < ApplicationRecord
has_one :account
has_one :account_history, through: :account
end
class Account < ApplicationRecord
belongs_to :supplier
has_one :account_history
end
class AccountHistory < ApplicationRecord
belongs_to :account
end
2.6 has_and_belongs_to_many
has_and_belongs_to_many 关联直接建立两个模型之间的多对多关系,不借由第三个模型。
例如,应用中有装配体和零件两个模型,每个装配体有多个零件,每个零件又可用于多个装配体(Assembly).
class Assembly < ApplicationRecord
has_and_belongs_to_many :parts
end
class Part < ApplicationRecord
has_and_belongs_to_many :assemblies
end
2.7 多态关联
关联还有一种高级形式——多态关联(polymorphic association)。在多态关联中,在同一个关联中,一个模型可以属于多个模型。
例如,图片模型可以属于雇员模型或者产品模型,
class Picture < ApplicationRecord
belongs_to :imageable, polymorphic: true
end
class Employee < ApplicationRecord
has_many :pictures, as: :imageable
end
class Product < ApplicationRecord
has_many :pictures, as: :imageable
end
在 belongs_to 中指定使用多态,可以理解成创建了一个接口,可供任何一个模型使用。
在 Employee 模型实例上,可以使用 @employee.pictures 获取图片集合。
类似地,可使用 @product.pictures 获取产品的图片。
2.8 自联结
设计数据模型时,模型有时要和自己建立关系。例如,在一个数据库表中保存所有雇员的信息,但要建立经理和下属之间的关系。这种情况可以使用自联结关联解决
class Employee < ApplicationRecord
has_many :subordinates, class_name: "Employee",
foreign_key: "manager_id"
belongs_to :manager, class_name: "Employee"
end
这样定义模型后,可以使用 @employee.subordinates 和 @employee.manager 检索了。
3 小技巧和注意事项
3.1 控制缓存
关联添加的方法都会使用缓存,记录最近一次查询的结果,以备后用。缓存还会在方法之间共享。
author.books # 从数据库中检索图书
author.books.size # 使用缓存的图书副本
author.books.empty? # 使用缓存的图书副本
author.books.reload.empty? # 丢掉缓存的图书副本
3.3 更新模式
3.3.1 创建 belongs_to 关联所需的外键
在 books 表中创建相应的外键,为了提升查询性能,最好为外键添加索引;为了保证参照完整性,最好为外键添加约束:
class CreateBooks < ActiveRecord::Migration[5.0]
def change
create_table :books do |t|
t.string :book_number
t.integer :author_id
end
add_index :books, :author_id
add_foreign_key :books, :authors
end
end
3.3.2 创建 has_and_belongs_to_many 关联所需的联结表
- 创建 has_and_belongs_to_many 关联后,必须手动创建联结表。除非使用 :join_table 选项指定了联结表的名称,否则 Active Record 会按照类名出现在字典中的顺序为表起名。因此,作者和图书模型使用的联结表默认名为“authors_books”,因为在字典中,“a”在“b”前面。
- 我们把 id: false 选项传给 create_table 方法,因为这个表不对应模型。只有这样,关联才能正常建立。
3.4 控制关联的作用域
默认情况下,关联只会查找当前模块作用域中的对象。
要想让处在不同命名空间中的模型正常建立关联,声明关联时要指定完整的类名:
module MyApplication
module Business
class Supplier < ApplicationRecord
has_one :account,
class_name: "MyApplication::Billing::Account"
end
end
module Billing
class Account < ApplicationRecord
belongs_to :supplier,
class_name: "MyApplication::Business::Supplier"
end
end
end
3.5 双向关联
一般情况下,都要求能在关联的两端进行操作,即在两个模型中都要声明关联。
class Author < ApplicationRecord
has_many :books
end
class Book < ApplicationRecord
belongs_to :author
end
通过关联的名称,Active Record 能探知这两个模型之间建立的是双向关联。这样一来,Active Record 只会加载一个 Author 对象副本,从而确保应用运行效率更高效,并避免数据不一致。
- Active Record 能自动识别多数具有标准名称的双向关联。然而,具有下述选项的关联无法识别:
:conditions
:through
:polymorphic
:class_name
:foreign_key - Active Record 提供了 :inverse_of 选项,可以通过它明确声明双向关联,inverse_of 有些限制:
不支持 :through 关联;
不支持 :polymorphic 关联;
不支持 :as 选项;
3.6 单表继承
$ rails generate model car --parent=Vehicle