1 为什么需要关联?
在Rails中,关联是两个Active Record模型之间的连接。为什么我们需要模型之间的关联?因为它们可以使代码中的常见操作更简单、更容易。
例如,考虑一个包含作者模型和图书模型的简单Rails应用程序。每个作者可以有多本图书。
如果没有关联,模型的声明将如下所示:
class Author < ApplicationRecord
end
class Book < ApplicationRecord
end
现在,假设我们想为现有作者添加一本新书。我们需要这样做:
@book = Book.create(published_at: Time.now, author_id: @author.id)
或者考虑删除一个作者,并确保其所有图书也被删除:
@books = Book.where(author_id: @author.id)
@books.each do |book|
book.destroy
end
@author.destroy
通过Active Record关联,我们可以通过声明告诉Rails这两个模型之间存在连接,从而简化这些操作和其他操作。下面是设置作者和图书的修订代码:
class Author < ApplicationRecord
has_many :books, dependent: :destroy
end
class Book < ApplicationRecord
belongs_to :author
end
通过这个改变,为特定作者创建一本新书更容易:
@book = @author.books.create(published_at: Time.now)
删除一个作者及其所有图书更容易:
author.destroy
要了解更多关联类型的信息,请阅读本指南的下一节。然后是一些关于使用关联的技巧和技巧,然后是Rails中关联的方法和选项的完整参考。
2 关联的类型
Rails支持六种类型的关联,每种类型都有特定的用例。
下面是所有支持的类型的列表,以及它们的API文档的链接,以获取有关如何使用它们、它们的方法参数等更详细的信息。
关联是使用宏样式调用来实现的,因此您可以声明性地向模型添加功能。例如,通过声明一个模型belongs_to
另一个模型,您指示Rails在这两个模型的实例之间维护主键-外键信息,并且您还可以获得一些添加到模型的实用方法。
在本指南的其余部分,您将学习如何声明和使用各种形式的关联。但首先,快速介绍每种关联类型适用的情况。
2.1 belongs_to
关联
belongs_to
关联建立了与另一个模型的连接,以便声明模型的每个实例“属于”另一个模型的一个实例。例如,如果您的应用程序包括作者和图书,并且每本图书可以分配给一个作者,您可以这样声明图书模型:
class Book < ApplicationRecord
belongs_to :author
end
注意:belongs_to
关联必须使用单数形式。如果在上面的示例中,Book
模型的author
关联使用了复数形式,并尝试通过Book.create(authors: author)
创建实例,您将收到“未初始化的常量Book::Authors”的错误。这是因为Rails会自动从关联名称推断类名。如果关联名称错误地使用了复数形式,那么推断出的类名也会错误地使用复数形式。
相应的迁移可能如下所示:
class CreateBooks < ActiveRecord::Migration[7.1]
def change
create_table :authors do |t|
t.string :name
t.timestamps
end
create_table :books do |t|
t.belongs_to :author
t.datetime :published_at
t.timestamps
end
end
end
当单独使用belongs_to
时,它会产生一个单向的一对一连接。因此,上面示例中的每本书“知道”它的作者,但作者不知道他们的书籍。
要设置双向关联 - 在另一个模型上使用belongs_to
与has_one
或has_many
结合使用,此处为Author模型。
ruby
create_table :books do |t|
t.belongs_to :author, foreign_key: true
# ...
end
2.2 has_one
关联
has_one
关联表示另一个模型对该模型有一个引用。可以通过该关联获取该模型。
例如,如果应用程序中的每个供应商只有一个帐户,可以这样声明供应商模型:
class Supplier < ApplicationRecord
has_one :account
end
与 belongs_to
的主要区别是链接列 supplier_id
位于另一个表中:
相应的迁移可能如下所示:
class CreateSuppliers < ActiveRecord::Migration[7.1]
def change
create_table :suppliers do |t|
t.string :name
t.timestamps
end
create_table :accounts do |t|
t.belongs_to :supplier
t.string :account_number
t.timestamps
end
end
end
根据用例的不同,您可能还需要在帐户表的供应商列上创建唯一索引和/或外键约束。在这种情况下,列定义可能如下所示:
create_table :accounts do |t|
t.belongs_to :supplier, index: { unique: true }, foreign_key: true
# ...
end
当与其他模型上的 belongs_to
结合使用时,此关系可以是双向的。
2.3 has_many
关联
has_many
关联类似于 has_one
,但表示与另一个模型的一对多连接。通常,您会在 belongs_to
关联的“另一侧”找到此关联。此关联表示模型的每个实例都可以有零个或多个另一个模型的实例。例如,在包含作者和书籍的应用程序中,可以这样声明作者模型:
class Author < ApplicationRecord
has_many :books
end
注意:声明 has_many
关联时,另一个模型的名称会被复数化。
相应的迁移可能如下所示:
class CreateAuthors < ActiveRecord::Migration[7.1]
def change
create_table :authors do |t|
t.string :name
t.timestamps
end
create_table :books do |t|
t.belongs_to :author
t.datetime :published_at
t.timestamps
end
end
end
根据用例,通常最好为书籍表的作者列创建一个非唯一索引,并可选地为其创建一个外键约束:
create_table :books do |t|
t.belongs_to :author, index: true, foreign_key: true
# ...
end
2.4 has_many :through
关联
has_many :through
关联通常用于建立与另一个模型的多对多连接。此关联表示声明模型可以通过第三个模型进行匹配,与另一个模型的零个或多个实例相关联。例如,考虑一个医疗实践,患者预约看医生。相关的关联声明可能如下所示:
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
相应的迁移可能如下所示:
class CreateAppointments < ActiveRecord::Migration[7.1]
def change
create_table :physicians do |t|
t.string :name
t.timestamps
end
create_table :patients do |t|
t.string :name
t.timestamps
end
create_table :appointments do |t|
t.belongs_to :physician
t.belongs_to :patient
t.datetime :appointment_date
t.timestamps
end
end
end
通过 has_many
关联方法,可以管理连接模型的集合。
例如,如果您分配:
physician.patients = patients
那么新的连接模型将自动为新关联的对象创建。 如果以前存在的一些对象现在缺失,则它们的连接行将自动删除。
警告:连接模型的自动删除是直接的,不会触发销毁回调。
has_many :through
关联还可用于通过嵌套的 has_many
关联设置“快捷方式”。例如,如果一个文档有多个部分,一个部分有多个段落,有时您可能希望获取文档中所有段落的简单集合。可以这样设置:
class Document < ApplicationRecord
has_many :sections
has_many :paragraphs, through: :sections
end
class Section < ApplicationRecord
belongs_to :document
has_many :paragraphs
end
class Paragraph < ApplicationRecord
belongs_to :section
end
通过指定 through: :sections
,Rails 现在可以理解:
@document.paragraphs
2.5 has_one :through
关联
has_one :through
关联建立了与另一个模型的一对一连接。此关联表示声明模型可以通过第三个模型进行匹配,与另一个模型的一个实例相关联。
例如,如果每个供应商都有一个帐户,并且每个帐户与一个帐户历史相关联,则供应商模型可能如下所示:
```ruby
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 ```
相应的迁移可能如下所示:
class CreateAccountHistories < ActiveRecord::Migration[7.1]
def change
create_table :suppliers do |t|
t.string :name
t.timestamps
end
create_table :accounts do |t|
t.belongs_to :supplier
t.string :account_number
t.timestamps
end
create_table :account_histories do |t|
t.belongs_to :account
t.integer :credit_rating
t.timestamps
end
end
end
2.6 has_and_belongs_to_many
关联
has_and_belongs_to_many
关联创建了一个直接的多对多连接,没有中间模型。
这个关联表示声明模型的每个实例都指向另一个模型的零个或多个实例。
例如,如果你的应用程序包括组装和零件,每个组装有多个零件,每个零件出现在多个组装中,你可以这样声明模型:
class Assembly < ApplicationRecord
has_and_belongs_to_many :parts
end
class Part < ApplicationRecord
has_and_belongs_to_many :assemblies
end
相应的迁移可能如下所示:
class CreateAssembliesAndParts < ActiveRecord::Migration[7.1]
def change
create_table :assemblies do |t|
t.string :name
t.timestamps
end
create_table :parts do |t|
t.string :part_number
t.timestamps
end
create_table :assemblies_parts, id: false do |t|
t.belongs_to :assembly
t.belongs_to :part
end
end
end
2.7 选择 belongs_to
和 has_one
之间的关系
如果你想在两个模型之间建立一对一关系,你需要在一个模型中添加 belongs_to
,在另一个模型中添加 has_one
。你如何知道哪个是哪个?
区别在于你在哪里放置外键(它放在声明 belongs_to
关联的类的表上),但你也应该考虑数据的实际含义。has_one
关系表示你拥有某个东西 - 也就是说,某个东西指向你。例如,说供应商拥有一个账户比说账户拥有一个供应商更有意义。这意味着正确的关系是这样的:
class Supplier < ApplicationRecord
has_one :account
end
class Account < ApplicationRecord
belongs_to :supplier
end
相应的迁移可能如下所示:
class CreateSuppliers < ActiveRecord::Migration[7.1]
def change
create_table :suppliers do |t|
t.string :name
t.timestamps
end
create_table :accounts do |t|
t.bigint :supplier_id
t.string :account_number
t.timestamps
end
add_index :accounts, :supplier_id
end
end
注意:使用 t.bigint :supplier_id
可以使外键命名明显和明确。在当前版本的 Rails 中,你可以使用 t.references :supplier
来抽象掉这个实现细节。
2.8 选择 has_many :through
和 has_and_belongs_to_many
之间的关系
Rails 提供了两种不同的方式来声明模型之间的多对多关系。第一种方式是使用 has_and_belongs_to_many
,它允许你直接建立关联:
class Assembly < ApplicationRecord
has_and_belongs_to_many :parts
end
class Part < ApplicationRecord
has_and_belongs_to_many :assemblies
end
声明多对多关系的第二种方式是使用 has_many :through
。这样可以通过一个关联模型间接建立关联:
class Assembly < ApplicationRecord
has_many :manifests
has_many :parts, through: :manifests
end
class Manifest < ApplicationRecord
belongs_to :assembly
belongs_to :part
end
class Part < ApplicationRecord
has_many :manifests
has_many :assemblies, through: :manifests
end
简单的经验法则是,如果你需要将关联模型作为独立实体进行操作,那么应该设置一个 has_many :through
关系。如果你不需要对关联模型进行任何操作,那么建立一个 has_and_belongs_to_many
关系可能更简单(尽管你需要记住在数据库中创建连接表)。
如果你需要在关联模型上进行验证、回调或添加额外属性,应该使用 has_many :through
。
2.9 多态关联
关联的一个稍微高级的变化是 多态关联。使用多态关联,一个模型可以属于多个其他模型,而且只有一个关联。例如,你可能有一个属于员工模型或产品模型的图片模型。下面是如何声明这个关联:
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
。
如果您有一个Picture
模型的实例,您可以通过@picture.imageable
访问其父级。为了使其工作,您需要在声明多态接口的模型中声明外键列和类型列:
class CreatePictures < ActiveRecord::Migration[7.1]
def change
create_table :pictures do |t|
t.string :name
t.bigint :imageable_id
t.string :imageable_type
t.timestamps
end
add_index :pictures, [:imageable_type, :imageable_id]
end
end
通过使用t.references
形式,可以简化此迁移:
class CreatePictures < ActiveRecord::Migration[7.1]
def change
create_table :pictures do |t|
t.string :name
t.references :imageable, polymorphic: true
t.timestamps
end
end
end
2.10 自连接
在设计数据模型时,有时会发现一个模型应该与自身有关系。例如,您可能希望将所有员工存储在单个数据库模型中,但能够追踪诸如经理和下属之间的关系。可以使用自连接关联来建模这种情况:
class Employee < ApplicationRecord
has_many :subordinates, class_name: "Employee",
foreign_key: "manager_id"
belongs_to :manager, class_name: "Employee", optional: true
end
通过这种设置,您可以检索@employee.subordinates
和@employee.manager
。
在您的迁移/模式中,您将向模型本身添加一个引用列。
class CreateEmployees < ActiveRecord::Migration[7.1]
def change
create_table :employees do |t|
t.references :manager, foreign_key: { to_table: :employees }
t.timestamps
end
end
end
注意:传递给foreign_key
的to_table
选项等更多选项在SchemaStatements#add_reference
中有解释。
3 提示、技巧和警告
以下是您在Rails应用程序中有效使用Active Record关联的一些要点:
- 控制缓存
- 避免名称冲突
- 更新模式
- 控制关联范围
- 双向关联
3.1 控制缓存
所有关联方法都是基于缓存构建的,它保持最近查询的结果可用于进一步操作。缓存甚至在方法之间共享。例如:
# 从数据库检索书籍
author.books.load
# 使用缓存的书籍副本
author.books.size
# 使用缓存的书籍副本
author.books.empty?
但是,如果您想重新加载缓存,因为数据可能已被应用程序的其他部分更改,只需在关联上调用reload
:
# 从数据库检索书籍
author.books.load
# 使用缓存的书籍副本
author.books.size
# 丢弃缓存的书籍副本并返回数据库
author.books.reload.empty?
3.2 避免名称冲突
您不能自由地为关联使用任何名称。因为创建关联会向模型添加具有该名称的方法,所以给关联一个已经用于ActiveRecord::Base
的实例方法的名称是一个坏主意。关联方法将覆盖基本方法并导致错误。例如,attributes
或connection
是关联的坏名称。
3.3 更新模式
关联非常有用,但它们并不是魔法。您负责维护数据库模式以匹配关联。实际上,这意味着根据您创建的关联类型,有两件事情需要做。对于belongs_to
关联,您需要创建外键;对于has_and_belongs_to_many
关联,您需要创建适当的连接表。
3.3.1 为belongs_to
关联创建外键
当您声明belongs_to
关联时,您需要根据需要创建外键。例如,考虑以下模型:
class Book < ApplicationRecord
belongs_to :author
end
此声明需要在books表中创建相应的外键列。对于全新的表,迁移可能如下所示:
class CreateBooks < ActiveRecord::Migration[7.1]
def change
create_table :books do |t|
t.datetime :published_at
t.string :book_number
t.references :author
end
end
end
而对于现有表,可能如下所示:
class AddAuthorToBooks < ActiveRecord::Migration[7.1]
def change
add_reference :books, :author
end
end
注意:如果您希望在数据库级别强制执行引用完整性,请将foreign_key: true
选项添加到上述“引用”列声明中。
3.3.2 为has_and_belongs_to_many
关联创建连接表
如果您创建了一个has_and_belongs_to_many
关联,您需要显式创建连接表。除非使用:join_table
选项显式指定连接表的名称,否则Active Record将使用类名的词法顺序创建名称。因此,作者和书籍模型之间的连接将给出默认连接表名称“authors_books”,因为在词法排序中,“a”优于“b”。
警告:模型名称之间的优先级是使用String
的<=>
运算符计算的。这意味着如果字符串的长度不同,并且在比较到最短长度时字符串相等,则较长的字符串被认为比较短的字符串具有更高的词法优先级。例如,人们期望表格"paper_boxes"和"papers"生成一个连接表名称为"papers_paper_boxes",因为名称"paper_boxes"的长度较长,但实际上生成的连接表名称是"paper_boxes_papers"(因为在常见编码中下划线'_'在字典顺序上小于's')。
无论名称如何,您都必须手动生成具有适当迁移的连接表。例如,考虑以下关联:
class Assembly < ApplicationRecord
has_and_belongs_to_many :parts
end
class Part < ApplicationRecord
has_and_belongs_to_many :assemblies
end
这些关联需要通过迁移来创建assemblies_parts
表。该表应该创建时没有主键:
class CreateAssembliesPartsJoinTable < ActiveRecord::Migration[7.1]
def change
create_table :assemblies_parts, id: false do |t|
t.bigint :assembly_id
t.bigint :part_id
end
add_index :assemblies_parts, :assembly_id
add_index :assemblies_parts, :part_id
end
end
我们在create_table
中传递id: false
,因为该表不表示一个模型。这对于关联的正常工作是必需的。如果您在has_and_belongs_to_many
关联中观察到任何奇怪的行为,比如混乱的模型ID或关于冲突ID的异常,那么很可能是忘记了这一点。
为了简化起见,您还可以使用create_join_table
方法:
class CreateAssembliesPartsJoinTable < ActiveRecord::Migration[7.1]
def change
create_join_table :assemblies, :parts do |t|
t.index :assembly_id
t.index :part_id
end
end
end
3.4 控制关联作用域
默认情况下,关联只在当前模块的作用域内查找对象。当您在一个模块内声明Active Record模型时,这可能很重要。例如:
module MyApplication
module Business
class Supplier < ApplicationRecord
has_one :account
end
class Account < ApplicationRecord
belongs_to :supplier
end
end
end
这将正常工作,因为Supplier
和Account
类都在同一个作用域内定义。但是以下情况将不起作用,因为Supplier
和Account
在不同的作用域中定义:
module MyApplication
module Business
class Supplier < ApplicationRecord
has_one :account
end
end
module Billing
class Account < ApplicationRecord
belongs_to :supplier
end
end
end
要将模型与不同命名空间中的模型关联起来,您必须在关联声明中指定完整的类名:
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:
避免对已加载数据进行不必要的查询:
irb> author = Author.first irb> author.books.all? do |book| irb> book.author.equal?(author) # 这里不会执行额外的查询 irb> end => true
避免不一致的数据(因为只加载了一个
Author
对象的副本):irb> author = Author.first irb> book = author.books.first irb> author.name == book.author.name => true irb> author.name = "Changed Name" irb> author.name == book.author.name => true
在更多情况下自动保存关联:
irb> author = Author.new irb> book = author.books.new irb> book.save! irb> book.persisted? => true irb> author.persisted? => true
irb> book = Book.new irb> book.valid? => false irb> book.errors.full_messages => ["Author must exist"] irb> author = Author.new irb> book = author.books.new irb> book.valid? => true
Active Record支持对大多数具有标准名称的关联进行自动识别。但是,包含:through
或:foreign_key
选项的双向关联将不会自动识别。
在相反关联上的自定义作用域也会阻止自动识别,就像在关联本身上的自定义作用域一样,除非将config.active_record.automatic_scope_inversing
设置为true(新应用程序的默认设置)。
例如,考虑以下模型声明:
class Author < ApplicationRecord
has_many :books
end
class Book < ApplicationRecord
belongs_to :writer, class_name: 'Author', foreign_key: 'author_id'
end
由于:foreign_key
选项,Active Record将不再自动识别双向关联。这可能会导致应用程序:
* 对相同数据执行不必要的查询(在这个例子中导致N+1查询):
```irb
irb> author = Author.first
irb> author.books.any? do |book|
irb> book.author.equal?(author) # 这会为每本书执行一次作者查询
irb> end
=> false
```
引用具有不一致数据的模型的多个副本:
irb> author = Author.first irb> book = author.books.first irb> author.name == book.author.name => true irb> author.name = "Changed Name" irb> author.name == book.author.name => false
未能自动保存关联:
irb> author = Author.new irb> book = author.books.new irb> book.save! irb> book.persisted? => true irb> author.persisted? => false
未能验证存在或不存在:
irb> author = Author.new irb> book = author.books.new irb> book.valid? => false irb> book.errors.full_messages => ["Author must exist"]
Active Record 提供了 :inverse_of
选项,因此您可以显式声明双向关联:
class Author < ApplicationRecord
has_many :books, inverse_of: 'writer'
end
class Book < ApplicationRecord
belongs_to :writer, class_name: 'Author', foreign_key: 'author_id'
end
通过在 has_many
关联声明中包含 :inverse_of
选项,Active Record 现在将识别双向关联,并像上面的初始示例一样运行。
4 详细的关联参考
以下部分详细介绍了每种类型的关联,包括它们添加的方法以及在声明关联时可以使用的选项。
4.1 belongs_to
关联参考
从数据库的角度来看,belongs_to
关联表示该模型的表包含一个表示对另一个表的引用的列。
这可以用于建立一对一或一对多的关系,具体取决于设置。
如果另一个类的表在一对一关系中包含引用,则应使用 has_one
。
4.1.1 belongs_to
添加的方法
当您声明 belongs_to
关联时,声明类会自动获得与关联相关的 8 个方法:
association
association=(associate)
build_association(attributes = {})
create_association(attributes = {})
create_association!(attributes = {})
reload_association
reset_association
association_changed?
association_previously_changed?
在所有这些方法中,association
都会被替换为作为 belongs_to
的第一个参数传递的符号。例如,给定以下声明:
class Book < ApplicationRecord
belongs_to :author
end
Book
模型的每个实例都会有这些方法:
author
author=
build_author
create_author
create_author!
reload_author
reset_author
author_changed?
author_previously_changed?
注意:当初始化一个新的 has_one
或 belongs_to
关联时,您必须使用 build_
前缀来构建关联,而不是用于 has_many
或 has_and_belongs_to_many
关联的 association.build
方法。要创建一个,使用 create_
前缀。
4.1.1.1 association
association
方法返回关联的对象(如果有)。如果没有找到关联的对象,则返回 nil
。
@author = @book.author
如果已经从数据库中检索到此对象的关联对象,则会返回缓存的版本。要覆盖此行为(并强制进行数据库读取),在父对象上调用 #reload_association
。
@author = @book.reload_author
要卸载关联对象的缓存版本(导致下一次访问(如果有)从数据库查询它),请在父对象上调用 #reset_association
。
@book.reset_author
4.1.1.2 association=(associate)
association=
方法将关联的对象分配给此对象。在幕后,这意味着从关联对象中提取主键,并将该对象的外键设置为相同的值。
@book.author = @author
4.1.1.3 build_association(attributes = {})
build_association
方法返回关联类型的新对象。此对象将从传递的属性实例化,并将通过此对象的外键设置链接,但关联对象尚未保存。
@author = @book.build_author(author_number: 123,
author_name: "John Doe")
4.1.1.4 create_association(attributes = {})
create_association
方法返回关联类型的新对象。此对象将从传递的属性实例化,并将通过此对象的外键设置链接,并且一旦通过关联模型上指定的所有验证,关联对象将被保存。
@author = @book.create_author(author_number: 123,
author_name: "John Doe")
4.1.1.5 create_association!(attributes = {})
与上面的 create_association
相同,但如果记录无效,则引发 ActiveRecord::RecordInvalid
。
4.1.1.6 association_changed?
association_changed?
方法返回 true,如果已分配新的关联对象,并且外键将在下一次保存时更新。
```ruby
@book.author # => #
@book.author = Author.second # => #
@book.save! @book.author_changed? # => false ```
4.1.1.7 association_previously_changed?
association_previously_changed?
方法在前一次保存更新关联引用到一个新的关联对象时返回true。
@book.author # => #<Author author_number: 123, author_name: "John Doe">
@book.author_previously_changed? # => false
@book.author = Author.second # => #<Author author_number: 456, author_name: "Jane Smith">
@book.save!
@book.author_previously_changed? # => true
4.1.2 belongs_to
的选项
虽然Rails使用智能默认值在大多数情况下都能很好地工作,但有时您可能想要自定义belongs_to
关联引用的行为。通过在创建关联时传递选项和作用域块,可以轻松实现这些自定义。例如,以下关联使用了两个这样的选项:
class Book < ApplicationRecord
belongs_to :author, touch: :books_updated_at,
counter_cache: true
end
belongs_to
关联支持以下选项:
:autosave
:class_name
:counter_cache
:dependent
:foreign_key
:primary_key
:inverse_of
:polymorphic
:touch
:validate
:optional
4.1.2.1 :autosave
如果将autosave
选项设置为true
,则在保存父对象时,Rails将保存任何已加载的关联成员,并销毁标记为销毁的成员。将autosave
设置为false
并不等同于不设置autosave
选项。如果不存在autosave
选项,则会保存新的关联对象,但不会保存已更新的关联对象。
4.1.2.2 :class_name
如果其他模型的名称无法从关联名称中推断出来,可以使用class_name
选项提供模型名称。例如,如果一本书属于一个作者,但包含作者的实际模型的名称是Patron
,则可以这样设置:
class Book < ApplicationRecord
belongs_to :author, class_name: "Patron"
end
4.1.2.3 :counter_cache
counter_cache
选项可用于提高查找所属对象数量的效率。考虑以下模型:
class Book < ApplicationRecord
belongs_to :author
end
class Author < ApplicationRecord
has_many :books
end
使用这些声明,要求author.books.size
的值需要调用数据库执行COUNT(*)
查询。为了避免这个调用,可以在所属模型上添加一个计数器缓存:
class Book < ApplicationRecord
belongs_to :author, counter_cache: true
end
class Author < ApplicationRecord
has_many :books
end
使用这个声明,Rails将保持缓存值的最新状态,并在size
方法的响应中返回该值。
虽然counter_cache
选项是在包含belongs_to
声明的模型上指定的,但实际的列必须添加到关联的(has_many
)模型中。在上面的例子中,您需要向Author
模型添加一个名为books_count
的列。
您可以通过在counter_cache
声明中指定自定义列名而覆盖默认列名,而不是使用true
。例如,使用count_of_books
代替books_count
:
class Book < ApplicationRecord
belongs_to :author, counter_cache: :count_of_books
end
class Author < ApplicationRecord
has_many :books
end
注意:只需要在关联的belongs_to
一侧指定counter_cache
选项。
通过attr_readonly
,计数器缓存列将添加到所有者模型的只读属性列表中。
如果由于某种原因更改所有者模型的主键的值,并且没有更新计数模型的外键,则计数器缓存可能具有过时的数据。换句话说,任何孤立的模型仍然会计入计数器。要修复过时的计数器缓存,请使用reset_counters
。
4.1.2.4 :dependent
如果将dependent
选项设置为:
:destroy
,当对象被销毁时,将在其关联对象上调用destroy
。:delete
,当对象被销毁时,将直接从数据库中删除其所有关联对象,而不调用其destroy
方法。:destroy_async
:当对象被销毁时,将会排队一个ActiveRecord::DestroyAssociationAsyncJob
作业,该作业将在其关联对象上调用destroy
。必须设置Active Job才能使用此选项。如果关联由数据库中的外键约束支持,请不要使用此选项。外键约束操作将在删除所有者的同一事务中发生。 警告:不应在与另一个类上的has_many
关联相连接的belongs_to
关联上指定此选项。这样做可能导致数据库中的孤立记录。
4.1.2.5 :foreign_key
按照惯例,Rails假设用于保存此模型上的外键的列名是关联名称加上后缀_id
。:foreign_key
选项允许您直接设置外键的名称:
class Book < ApplicationRecord
belongs_to :author, class_name: "Patron",
foreign_key: "patron_id"
end
提示:无论如何,Rails都不会为您创建外键列。您需要在迁移的一部分中明确定义它们。
4.1.2.6 :primary_key
按照惯例,Rails假设id
列用于保存其表的主键。:primary_key
选项允许您指定不同的列。
例如,假设我们有一个具有guid
作为主键的users
表。如果我们想要一个单独的todos
表来保存guid
列中的外键user_id
,那么我们可以使用primary_key
来实现:
class User < ApplicationRecord
self.primary_key = 'guid' # 主键是guid而不是id
end
class Todo < ApplicationRecord
belongs_to :user, primary_key: 'guid'
end
当我们执行@user.todos.create
时,@todo
记录的user_id
值将是@user
的guid
值。
4.1.2.7 :inverse_of
:inverse_of
选项指定与此关联相反的has_many
或has_one
关联的名称。有关详细信息,请参见双向关联部分。
class Author < ApplicationRecord
has_many :books, inverse_of: :author
end
class Book < ApplicationRecord
belongs_to :author, inverse_of: :books
end
4.1.2.8 :polymorphic
将true
传递给:polymorphic
选项表示这是一个多态关联。多态关联在本指南的多态关联部分中有详细讨论。
4.1.2.9 :touch
如果将:touch
选项设置为true
,则在保存或删除此对象时,关联对象上的updated_at
或updated_on
时间戳将设置为当前时间:
class Book < ApplicationRecord
belongs_to :author, touch: true
end
class Author < ApplicationRecord
has_many :books
end
在这种情况下,保存或删除一本书将更新关联作者上的时间戳。您还可以指定要更新的特定时间戳属性:
class Book < ApplicationRecord
belongs_to :author, touch: :books_updated_at
end
4.1.2.10 :validate
如果将:validate
选项设置为true
,则在保存此对象时,新的关联对象将进行验证。默认情况下,此选项为false
:在保存此对象时,不会验证新的关联对象。
4.1.2.11 :optional
如果将:optional
选项设置为true
,则不会验证关联对象的存在。默认情况下,此选项设置为false
。
4.1.3 belongs_to
的作用域
有时您可能希望自定义belongs_to
使用的查询。可以通过作用域块来实现此类自定义。例如:
class Book < ApplicationRecord
belongs_to :author, -> { where active: true }
end
您可以在作用域块中使用任何标准的查询方法。下面讨论了以下方法:
where
includes
readonly
select
4.1.3.1 where
where
方法允许您指定关联对象必须满足的条件。
class Book < ApplicationRecord
belongs_to :author, -> { where active: true }
end
4.1.3.2 includes
您可以使用includes
方法来指定在使用此关联时应预加载的二级关联。例如,考虑以下模型:
class Chapter < ApplicationRecord
belongs_to :book
end
class Book < ApplicationRecord
belongs_to :author
has_many :chapters
end
class Author < ApplicationRecord
has_many :books
end
如果您经常直接从章节中检索作者(@chapter.book.author
),那么您可以通过在章节到书籍的关联中包含作者来使代码更高效:
class Chapter < ApplicationRecord
belongs_to :book, -> { includes :author }
end
class Book < ApplicationRecord
belongs_to :author
has_many :chapters
end
class Author < ApplicationRecord
has_many :books
end
注意:对于直接关联,无需使用includes
- 也就是说,如果您有Book belongs_to :author
,则在需要时会自动预加载作者。
4.1.3.3 readonly
如果使用readonly
,则通过关联检索到的关联对象将为只读。
4.1.3.4 select
select
方法允许你覆盖用于检索关联对象数据的 SQL SELECT
子句。默认情况下,Rails 检索所有列。
提示:如果你在 belongs_to
关联上使用 select
方法,你还应该设置 :foreign_key
选项以确保正确的结果。
4.1.4 是否存在任何关联对象?
你可以使用 association.nil?
方法来查看是否存在任何关联对象:
if @book.author.nil?
@msg = "找不到此书的作者"
end
4.1.5 对象何时保存?
将对象分配给 belongs_to
关联不会自动保存对象。它也不会保存关联对象。
4.2 has_one
关联参考
has_one
关联创建与另一个模型的一对一匹配。在数据库术语中,此关联表示另一个类包含外键。如果此类包含外键,则应该使用 belongs_to
。
4.2.1 has_one
添加的方法
当声明 has_one
关联时,声明类会自动获得与关联相关的 6 个方法:
association
association=(associate)
build_association(attributes = {})
create_association(attributes = {})
create_association!(attributes = {})
reload_association
reset_association
在所有这些方法中,association
都会被替换为作为第一个参数传递给 has_one
的符号。例如,给定以下声明:
class Supplier < ApplicationRecord
has_one :account
end
Supplier
模型的每个实例都会有这些方法:
account
account=
build_account
create_account
create_account!
reload_account
reset_account
注意:当初始化一个新的 has_one
或 belongs_to
关联时,你必须使用 build_
前缀来构建关联,而不是用于 has_many
或 has_and_belongs_to_many
关联的 association.build
方法。要创建一个关联对象,使用 create_
前缀。
4.2.1.1 association
association
方法返回关联对象(如果有)。如果没有找到关联对象,则返回 nil
。
@account = @supplier.account
如果已经从数据库中检索到此对象的关联对象,则会返回缓存的版本。要覆盖此行为(并强制进行数据库读取),在父对象上调用 #reload_association
。
@account = @supplier.reload_account
要卸载关联对象的缓存版本(强制下一次访问(如果有)从数据库查询它),在父对象上调用 #reset_association
。
@supplier.reset_account
4.2.1.2 association=(associate)
association=
方法将关联对象分配给此对象。在幕后,这意味着从此对象中提取主键,并将关联对象的外键设置为相同的值。
@supplier.account = @account
4.2.1.3 build_association(attributes = {})
build_association
方法返回关联类型的新对象。此对象将从传递的属性实例化,并通过其外键设置链接,但关联对象尚未保存。
@account = @supplier.build_account(terms: "Net 30")
4.2.1.4 create_association(attributes = {})
create_association
方法返回关联类型的新对象。此对象将从传递的属性实例化,并通过其外键设置链接,并且一旦通过关联模型上指定的所有验证,关联对象将被保存。
@account = @supplier.create_account(terms: "Net 30")
4.2.1.5 create_association!(attributes = {})
与上面的 create_association
相同,但如果记录无效,则引发 ActiveRecord::RecordInvalid
。
4.2.2 has_one
的选项
虽然 Rails 使用智能默认值,在大多数情况下都能很好地工作,但有时你可能想要自定义 has_one
关联参考的行为。通过在创建关联时传递选项,可以轻松实现这些自定义。例如,此关联使用了两个选项:
class Supplier < ApplicationRecord
has_one :account, class_name: "Billing", dependent: :nullify
end
has_one
关联支持以下选项:
:as
:autosave
:class_name
:dependent
:foreign_key
:inverse_of
:primary_key
:source
:source_type
:through
:touch
:validate
4.2.2.1 :as
设置 :as
选项表示这是一个多态关联。多态关联在本指南的前面部分中有详细讨论。
4.2.2.2 :autosave
如果将 :autosave
选项设置为 true
,Rails 将在保存父对象时保存任何已加载的关联成员,并销毁标记为销毁的成员。将 :autosave
设置为 false
不等同于不设置 :autosave
选项。如果不存在 :autosave
选项,则新的关联对象将被保存,但更新的关联对象将不会被保存。
4.2.2.3 :class_name
如果其他模型的名称不能从关联名称中推导出来,您可以使用:class_name
选项来提供模型名称。例如,如果供应商有一个账户,但包含账户的实际模型名称是Billing
,您可以这样设置:
class Supplier < ApplicationRecord
has_one :account, class_name: "Billing"
end
4.2.2.4 :dependent
控制当所有者被销毁时,关联对象会发生什么:
:destroy
会导致关联对象也被销毁:delete
会直接从数据库中删除关联对象(因此回调不会执行):destroy_async
:当对象被销毁时,会排队一个ActiveRecord::DestroyAssociationAsyncJob
作业,该作业将调用其关联对象的销毁方法。必须设置Active Job才能使其工作。如果关联由数据库中的外键约束支持,请不要使用此选项。外键约束操作将在删除所有者的同一事务中发生。:nullify
会将外键设置为NULL
。在多态关联中,多态类型列也会被设置为NULL
。不会执行回调。:restrict_with_exception
会在存在关联记录时引发ActiveRecord::DeleteRestrictionError
异常:restrict_with_error
会在所有者上添加错误,如果存在关联对象
对于那些具有NOT NULL
数据库约束的关联,不设置或保留:nullify
选项是必要的。如果不将dependent
设置为销毁此类关联,您将无法更改关联对象,因为初始关联对象的外键将被设置为不允许的NULL
值。
4.2.2.5 :foreign_key
按照惯例,Rails假设用于保存其他模型上的外键的列名是该模型的名称加上后缀_id
。:foreign_key
选项允许您直接设置外键的名称:
class Supplier < ApplicationRecord
has_one :account, foreign_key: "supp_id"
end
提示:无论如何,Rails都不会为您创建外键列。您需要在迁移的一部分中明确定义它们。
4.2.2.6 :inverse_of
:inverse_of
选项指定与此关联相反的belongs_to
关联的名称。有关更多详细信息,请参见双向关联部分。
class Supplier < ApplicationRecord
has_one :account, inverse_of: :supplier
end
class Account < ApplicationRecord
belongs_to :supplier, inverse_of: :account
end
4.2.2.7 :primary_key
按照惯例,Rails假设用于保存此模型的主键的列是id
。您可以通过:primary_key
选项覆盖这一点并明确指定主键。
4.2.2.8 :source
:source
选项指定has_one :through
关联的源关联名称。
4.2.2.9 :source_type
:source_type
选项指定通过多态关联进行的has_one :through
关联的源关联类型。
class Author < ApplicationRecord
has_one :book
has_one :hardback, through: :book, source: :format, source_type: "Hardback"
has_one :dust_jacket, through: :hardback
end
class Book < ApplicationRecord
belongs_to :format, polymorphic: true
end
class Paperback < ApplicationRecord; end
class Hardback < ApplicationRecord
has_one :dust_jacket
end
class DustJacket < ApplicationRecord; end
4.2.2.10 :through
:through
选项指定一个联接模型,通过该模型执行查询。关于has_one :through
关联的详细讨论可以在本指南的前面部分找到。
4.2.2.11 :touch
如果将:touch
选项设置为true
,那么在保存或销毁此对象时,关联对象上的updated_at
或updated_on
时间戳将被设置为当前时间:
class Supplier < ApplicationRecord
has_one :account, touch: true
end
class Account < ApplicationRecord
belongs_to :supplier
end
在这种情况下,保存或销毁供应商将更新关联账户上的时间戳。您还可以指定要更新的特定时间戳属性:
class Supplier < ApplicationRecord
has_one :account, touch: :suppliers_updated_at
end
4.2.2.12 :validate
如果将:validate
选项设置为true
,则在保存此对象时,新的关联对象将被验证。默认情况下,这是false
:在保存此对象时,新的关联对象不会被验证。
4.2.3 has_one
的作用域
有时您可能希望自定义has_one
使用的查询。可以通过作用域块来实现这样的自定义。例如:
class Supplier < ApplicationRecord
has_one :account, -> { where active: true }
end
您可以在作用域块中使用任何标准的查询方法。以下是讨论的几种方法:
where
includes
readonly
select
4.2.3.1 where
where
方法允许您指定关联对象必须满足的条件。
class Supplier < ApplicationRecord
has_one :account, -> { where "confirmed = 1" }
end
4.2.3.2 includes
您可以使用includes
方法来指定在使用此关联时应预加载的二级关联。例如,考虑以下模型:
class Supplier < ApplicationRecord
has_one :account
end
class Account < ApplicationRecord
belongs_to :supplier
belongs_to :representative
end
class Representative < ApplicationRecord
has_many :accounts
end
如果您经常直接从供应商检索代表(@supplier.account.representative
),则可以通过在供应商到帐户的关联中包含代表来使代码更高效:
class Supplier < ApplicationRecord
has_one :account, -> { includes :representative }
end
class Account < ApplicationRecord
belongs_to :supplier
belongs_to :representative
end
class Representative < ApplicationRecord
has_many :accounts
end
4.2.3.3 readonly
如果使用readonly
方法,则通过关联检索到的关联对象将为只读。
4.2.3.4 select
select
方法允许您覆盖用于检索关联对象的SQL SELECT
子句。默认情况下,Rails检索所有列。
4.2.4 是否存在任何关联对象?
您可以使用association.nil?
方法来查看是否存在任何关联对象:
if @supplier.account.nil?
@msg = "No account found for this supplier"
end
4.2.5 何时保存对象?
当您将对象分配给has_one
关联时,该对象会自动保存(以更新其外键)。此外,任何被替换的对象也会自动保存,因为其外键也会更改。
如果由于验证错误而导致这些保存之一失败,则赋值语句将返回false
,并且赋值本身将被取消。
如果父对象(声明has_one
关联的对象)未保存(即new_record?
返回true
),则子对象不会保存。它们将在父对象保存时自动保存。
如果要将对象分配给has_one
关联而不保存该对象,请使用build_association
方法。
4.3 has_many
关联参考
has_many
关联创建与另一个模型的一对多关系。在数据库术语中,此关联表示另一个类将具有引用到此类实例的外键。
4.3.1 has_many
添加的方法
当声明has_many
关联时,声明类会自动获得与关联相关的17个方法:
collection
collection<<(object, ...)
collection.delete(object, ...)
collection.destroy(object, ...)
collection=(objects)
collection_singular_ids
collection_singular_ids=(ids)
collection.clear
collection.empty?
collection.size
collection.find(...)
collection.where(...)
collection.exists?(...)
collection.build(attributes = {})
collection.create(attributes = {})
collection.create!(attributes = {})
collection.reload
在所有这些方法中,collection
将被替换为传递给has_many
的第一个参数的符号,collection_singular
将被替换为该符号的单数形式。例如,给定以下声明:
class Author < ApplicationRecord
has_many :books
end
Author
模型的每个实例将具有以下方法:
books
books<<(object, ...)
books.delete(object, ...)
books.destroy(object, ...)
books=(objects)
book_ids
book_ids=(ids)
books.clear
books.empty?
books.size
books.find(...)
books.where(...)
books.exists?(...)
books.build(attributes = {}, ...)
books.create(attributes = {})
books.create!(attributes = {})
books.reload
4.3.1.1 collection
collection
方法返回与所有关联对象相关的关系。如果没有关联对象,则返回一个空的关系。
@books = @author.books
4.3.1.2 collection<<(object, ...)
collection<<
方法通过将其外键设置为调用模型的主键,将一个或多个对象添加到集合中。
@author.books << @book1
4.3.1.3 collection.delete(object, ...)
collection.delete
方法通过将其外键设置为NULL
,从集合中删除一个或多个对象。
@author.books.delete(@book1)
警告:如果与dependent: :destroy
相关联,对象将被销毁;如果与dependent: :delete_all
相关联,对象将被删除。
4.3.1.4 collection.destroy(object, ...)
collection.destroy
方法通过在每个对象上运行destroy
,从集合中删除一个或多个对象。
@author.books.destroy(@book1)
警告:对象将始终从数据库中删除,忽略dependent
选项。
4.3.1.5 collection=(objects)
collection=
方法通过适当地添加和删除来使集合仅包含提供的对象。更改将持久保存到数据库中。
4.3.1.6 collection_singular_ids
collection_singular_ids
方法返回集合中对象的 id 数组。
@book_ids = @author.book_ids
4.3.1.7 collection_singular_ids=(ids)
collection_singular_ids=
方法通过添加和删除适当的方式,使集合仅包含由提供的主键值标识的对象。更改将持久化到数据库中。
4.3.1.8 collection.clear
collection.clear
方法根据 dependent
选项指定的策略从集合中移除所有对象。如果没有给出选项,则遵循默认策略。has_many :through
关联的默认策略是 delete_all
,而 has_many
关联的默认策略是将外键设置为 NULL
。
@author.books.clear
警告:如果对象与 dependent: :destroy
或 dependent: :destroy_async
相关联,它们将被删除,就像 dependent: :delete_all
一样。
4.3.1.9 collection.empty?
collection.empty?
方法在集合不包含任何关联对象时返回 true
。
<% if @author.books.empty? %>
未找到书籍
<% end %>
4.3.1.10 collection.size
collection.size
方法返回集合中的对象数量。
@book_count = @author.books.size
4.3.1.11 collection.find(...)
collection.find
方法在集合的表中查找对象。
@available_book = @author.books.find(1)
4.3.1.12 collection.where(...)
collection.where
方法根据提供的条件在集合中查找对象,但对象是惰性加载的,这意味着只有在访问对象时才会查询数据库。
@available_books = author.books.where(available: true) # 还没有查询
@available_book = @available_books.first # 现在将查询数据库
4.3.1.13 collection.exists?(...)
collection.exists?
方法检查集合的表中是否存在满足提供的条件的对象。
4.3.1.14 collection.build(attributes = {})
collection.build
方法返回关联类型的一个或多个新对象。对象将从传递的属性实例化,并创建通过它们的外键的链接,但关联对象尚未保存。
@book = author.books.build(published_at: Time.now,
book_number: "A12345")
@books = author.books.build([
{ published_at: Time.now, book_number: "A12346" },
{ published_at: Time.now, book_number: "A12347" }
])
4.3.1.15 collection.create(attributes = {})
collection.create
方法返回关联类型的一个或多个新对象。对象将从传递的属性实例化,并创建通过它们的外键的链接,并且一旦通过关联模型上指定的所有验证,关联对象将被保存。
@book = author.books.create(published_at: Time.now,
book_number: "A12345")
@books = author.books.create([
{ published_at: Time.now, book_number: "A12346" },
{ published_at: Time.now, book_number: "A12347" }
])
4.3.1.16 collection.create!(attributes = {})
与上面的 collection.create
相同,但如果记录无效,则引发 ActiveRecord::RecordInvalid
。
4.3.1.17 collection.reload
collection.reload
方法返回所有关联对象的关系,强制进行数据库读取。如果没有关联对象,则返回一个空的关系。
@books = author.books.reload
4.3.2 has_many
的选项
虽然 Rails 使用智能默认值,在大多数情况下都能很好地工作,但有时您可能希望自定义 has_many
关联引用的行为。通过在创建关联时传递选项,可以轻松实现这些自定义。例如,此关联使用了两个这样的选项:
class Author < ApplicationRecord
has_many :books, dependent: :delete_all, validate: false
end
has_many
关联支持以下选项:
:as
:autosave
:class_name
:counter_cache
:dependent
:foreign_key
:inverse_of
:primary_key
:source
:source_type
:through
:validate
4.3.2.1 :as
设置 :as
选项表示这是一个多态关联,如本指南前面所讨论的。
4.3.2.2 :autosave
如果将 :autosave
选项设置为 true
,Rails 将在保存父对象时保存任何加载的关联成员,并销毁标记为删除的成员。将 :autosave
设置为 false
不等同于不设置 :autosave
选项。如果不存在 :autosave
选项,则会保存新的关联对象,但不会保存已更新的关联对象。
4.3.2.3 :class_name
如果其他模型的名称无法从关联名称推导出来,可以使用 :class_name
选项提供模型名称。例如,如果作者有多本书,但包含书籍的实际模型的名称是 Transaction
,可以这样设置:
class Author < ApplicationRecord
has_many :books, class_name: "Transaction"
end
4.3.2.4 :counter_cache
此选项可用于配置自定义的 :counter_cache
名称。只有在自定义了 belongs_to 关联 的 :counter_cache
名称时才需要使用此选项。
4.3.2.5 :dependent
控制当所有者对象被销毁时,关联对象会发生什么:
:destroy
导致所有关联对象也被销毁:delete_all
直接从数据库中删除所有关联对象(因此不会执行回调):destroy_async
:当对象被销毁时,会排队一个ActiveRecord::DestroyAssociationAsyncJob
作业,该作业将调用其关联对象的 destroy 方法。必须设置 Active Job 才能使其工作。:nullify
将外键设置为NULL
。对于多态关联,多态类型列也将被设置为 null。不执行回调。:restrict_with_exception
如果存在任何关联记录,则引发ActiveRecord::DeleteRestrictionError
异常:restrict_with_error
如果存在任何关联对象,则向所有者添加错误
:destroy
和 :delete_all
选项还会影响 collection.delete
和 collection=
方法的语义,当从集合中移除对象时,它们会销毁关联对象。
4.3.2.6 :foreign_key
按照约定,Rails 假设在其他模型上用于保存外键的列的名称是该模型的名称加上后缀 _id
。:foreign_key
选项允许您直接设置外键的名称:
class Author < ApplicationRecord
has_many :books, foreign_key: "cust_id"
end
提示:无论如何,Rails 都不会为您创建外键列。您需要在迁移的一部分中明确定义它们。
4.3.2.7 :inverse_of
:inverse_of
选项指定与此关联相反的 belongs_to
关联的名称。有关更多详细信息,请参见双向关联部分。
class Author < ApplicationRecord
has_many :books, inverse_of: :author
end
class Book < ApplicationRecord
belongs_to :author, inverse_of: :books
end
4.3.2.8 :primary_key
按照约定,Rails 假设用于保存关联的主键的列是 id
。您可以使用 :primary_key
选项覆盖此设置并显式指定主键。
假设 users
表的主键是 id
,但它还有一个 guid
列。要求是 todos
表应将 guid
列的值作为外键,而不是 id
值。可以通过以下方式实现:
class User < ApplicationRecord
has_many :todos, primary_key: :guid
end
现在,如果我们执行 @todo = @user.todos.create
,那么 @todo
记录的 user_id
值将是 @user
的 guid
值。
4.3.2.9 :source
:source
选项指定 has_many :through
关联的源关联名称。只有当源关联的名称无法从关联名称自动推断出来时,才需要使用此选项。
4.3.2.10 :source_type
:source_type
选项指定通过多态关联进行的 has_many :through
关联的源关联类型。
class Author < ApplicationRecord
has_many :books
has_many :paperbacks, through: :books, source: :format, source_type: "Paperback"
end
class Book < ApplicationRecord
belongs_to :format, polymorphic: true
end
class Hardback < ApplicationRecord; end
class Paperback < ApplicationRecord; end
4.3.2.11 :through
:through
选项指定一个连接模型,通过该模型执行查询。has_many :through
关联提供了一种实现多对多关系的方法,如本指南前面讨论的。
4.3.2.12 :validate
如果将 :validate
选项设置为 false
,则每当保存此对象时,新的关联对象将不会进行验证。默认情况下,此值为 true
:保存此对象时,新的关联对象将进行验证。
4.3.3 has_many
的作用域
有时您可能希望自定义 has_many
使用的查询。可以通过作用域块来实现此类自定义。例如:
class Author < ApplicationRecord
has_many :books, -> { where processed: true }
end
您可以在作用域块中使用任何标准的查询方法。以下是讨论的一些方法:
where
extending
group
includes
limit
offset
order
readonly
select
distinct
4.3.3.1 where
where
方法允许您指定关联对象必须满足的条件。
class Author < ApplicationRecord
has_many :confirmed_books, -> { where "confirmed = 1" },
class_name: "Book"
end
您还可以通过哈希设置条件:
class Author < ApplicationRecord
has_many :confirmed_books, -> { where confirmed: true },
class_name: "Book"
end
如果您使用哈希风格的where
选项,那么通过该关联创建的记录将自动使用哈希进行范围限定。在这种情况下,使用author.confirmed_books.create
或author.confirmed_books.build
将创建confirmed
列值为true
的书籍。
4.3.3.2 extending
extending
方法指定要扩展关联代理的命名模块。关联扩展在本指南的后面部分详细讨论。
4.3.3.3 group
group
方法提供一个属性名称,用于使用GROUP BY
子句在查找器SQL中对结果集进行分组。
class Author < ApplicationRecord
has_many :chapters, -> { group 'books.id' },
through: :books
end
4.3.3.4 includes
您可以使用includes
方法指定在使用此关联时应预加载的二级关联。例如,考虑以下模型:
class Author < ApplicationRecord
has_many :books
end
class Book < ApplicationRecord
belongs_to :author
has_many :chapters
end
class Chapter < ApplicationRecord
belongs_to :book
end
如果您经常直接从作者中检索章节(author.books.chapters
),那么通过在从作者到书籍的关联中包含章节,可以使您的代码更加高效:
class Author < ApplicationRecord
has_many :books, -> { includes :chapters }
end
class Book < ApplicationRecord
belongs_to :author
has_many :chapters
end
class Chapter < ApplicationRecord
belongs_to :book
end
4.3.3.5 limit
limit
方法允许您限制通过关联获取的对象的总数。
class Author < ApplicationRecord
has_many :recent_books,
-> { order('published_at desc').limit(100) },
class_name: "Book"
end
4.3.3.6 offset
offset
方法允许您指定通过关联获取对象的起始偏移量。例如,-> { offset(11) }
将跳过前11条记录。
4.3.3.7 order
order
方法指定关联对象接收的顺序(使用SQL ORDER BY
子句的语法)。
class Author < ApplicationRecord
has_many :books, -> { order "date_confirmed DESC" }
end
4.3.3.8 readonly
如果使用readonly
方法,则通过关联检索的关联对象将为只读。
4.3.3.9 select
select
方法允许您覆盖用于检索关联对象数据的SQL SELECT
子句。默认情况下,Rails检索所有列。
警告:如果指定自己的select
,请确保包括关联模型的主键和外键列。如果不包括,Rails将抛出错误。
4.3.3.10 distinct
使用distinct
方法使集合中不包含重复项。这在与:through
选项一起使用时非常有用。
class Person < ApplicationRecord
has_many :readings
has_many :articles, through: :readings
end
irb> person = Person.create(name: 'John')
irb> article = Article.create(name: 'a1')
irb> person.articles << article
irb> person.articles << article
irb> person.articles.to_a
=> [#<Article id: 5, name: "a1">, #<Article id: 5, name: "a1">]
irb> Reading.all.to_a
=> [#<Reading id: 12, person_id: 5, article_id: 5>, #<Reading id: 13, person_id: 5, article_id: 5>]
在上述情况下,有两个读物,person.articles
将它们都显示出来,即使这些记录指向同一篇文章。
现在让我们设置distinct
:
class Person
has_many :readings
has_many :articles, -> { distinct }, through: :readings
end
irb> person = Person.create(name: 'Honda')
irb> article = Article.create(name: 'a1')
irb> person.articles << article
irb> person.articles << article
irb> person.articles.to_a
=> [#<Article id: 7, name: "a1">]
irb> Reading.all.to_a
=> [#<Reading id: 16, person_id: 7, article_id: 7>, #<Reading id: 17, person_id: 7, article_id: 7>]
在上述情况下,仍然有两个读物。但是,person.articles
只显示一篇文章,因为集合仅加载唯一的记录。
如果您希望确保在插入时,持久化关联中的所有记录都是唯一的(以便您可以确保在检查关联时永远不会找到重复的记录),您应该在表本身上添加唯一索引。例如,如果您有一个名为readings
的表,并且希望确保文章只能添加到一个人一次,您可以在迁移中添加以下内容:
add_index :readings, [:person_id, :article_id], unique: true
一旦您拥有了这个唯一索引,尝试将文章添加到一个人两次将会引发ActiveRecord::RecordNotUnique
错误:
irb> person = Person.create(name: 'Honda')
irb> article = Article.create(name: 'a1')
irb> person.articles << article
irb> person.articles << article
ActiveRecord::RecordNotUnique
请注意,使用include?
来检查唯一性存在竞争条件。不要尝试使用include?
来强制关联中的唯一性。例如,使用上面的文章示例,以下代码将存在竞争条件,因为多个用户可能同时尝试执行此操作:
person.articles << article unless person.articles.include?(article)
4.3.4 对象何时保存?
当您将一个对象分配给has_many
关联时,该对象会自动保存(以更新其外键)。如果您在一条语句中分配多个对象,则它们都会被保存。
如果由于验证错误而导致任何保存失败,则赋值语句将返回false
,并且赋值本身将被取消。
如果父对象(声明has_many
关联的对象)未保存(即new_record?
返回true
),则在添加它们时不会保存子对象。当父对象保存时,所有未保存的关联成员将自动保存。
如果您想将一个对象分配给has_many
关联而不保存该对象,请使用collection.build
方法。
4.4 has_and_belongs_to_many
关联参考
has_and_belongs_to_many
关联创建了与另一个模型的多对多关系。在数据库术语中,这通过一个包含指向每个类的外键的中间连接表将两个类关联起来。
4.4.1 has_and_belongs_to_many
添加的方法
当您声明一个has_and_belongs_to_many
关联时,声明类会自动获得与关联相关的几个方法:
collection
collection<<(object, ...)
collection.delete(object, ...)
collection.destroy(object, ...)
collection=(objects)
collection_singular_ids
collection_singular_ids=(ids)
collection.clear
collection.empty?
collection.size
collection.find(...)
collection.where(...)
collection.exists?(...)
collection.build(attributes = {})
collection.create(attributes = {})
collection.create!(attributes = {})
collection.reload
在所有这些方法中,collection
被替换为作为has_and_belongs_to_many
的第一个参数传递的符号,并且collection_singular
被替换为该符号的单数形式。例如,给定以下声明:
class Part < ApplicationRecord
has_and_belongs_to_many :assemblies
end
Part
模型的每个实例都将具有这些方法:
assemblies
assemblies<<(object, ...)
assemblies.delete(object, ...)
assemblies.destroy(object, ...)
assemblies=(objects)
assembly_ids
assembly_ids=(ids)
assemblies.clear
assemblies.empty?
assemblies.size
assemblies.find(...)
assemblies.where(...)
assemblies.exists?(...)
assemblies.build(attributes = {}, ...)
assemblies.create(attributes = {})
assemblies.create!(attributes = {})
assemblies.reload
4.4.1.1 附加列方法
如果has_and_belongs_to_many
关联的连接表除了两个外键之外还有其他列,则这些列将作为属性添加到通过该关联检索的记录中。带有附加属性返回的记录始终是只读的,因为Rails无法保存对这些属性的更改。
警告:在has_and_belongs_to_many
关联的连接表上使用额外属性已被弃用。如果您需要在连接两个模型的多对多关系的表上使用此类复杂行为,应该使用has_many :through
关联而不是has_and_belongs_to_many
。
4.4.1.2 collection
collection
方法返回与所有关联对象相关的关系。如果没有关联对象,则返回一个空的关系。
@assemblies = @part.assemblies
4.4.1.3 collection<<(object, ...)
collection<<
方法通过在连接表中创建记录将一个或多个对象添加到集合中。
@part.assemblies << @assembly1
注意:此方法别名为collection.concat
和collection.push
。
4.4.1.4 collection.delete(object, ...)
collection.delete
方法通过在连接表中删除记录来从集合中删除一个或多个对象。这不会销毁对象。
@part.assemblies.delete(@assembly1)
4.4.1.5 collection.destroy(object, ...)
collection.destroy
方法通过在连接表中删除记录来从集合中删除一个或多个对象。这不会销毁对象。
@part.assemblies.destroy(@assembly1)
4.4.1.6 collection=(objects)
collection=
方法使集合仅包含提供的对象,通过适当地添加和删除。更改将持久保存到数据库中。
4.4.1.7 collection_singular_ids
collection_singular_ids
方法返回集合中对象的id数组。
@assembly_ids = @part.assembly_ids
4.4.1.8 collection_singular_ids=(ids)
collection_singular_ids=
方法使集合仅包含由提供的主键值标识的对象,通过适当地添加和删除。更改将持久保存到数据库中。
4.4.1.9 collection.clear
collection.clear
方法通过从连接表中删除行来从集合中删除每个对象。这不会销毁关联的对象。
4.4.1.10 collection.empty?
collection.empty?
方法如果集合不包含任何关联对象,则返回 true
。
<% if @part.assemblies.empty? %>
This part is not used in any assemblies
<% end %>
4.4.1.11 collection.size
collection.size
方法返回集合中对象的数量。
@assembly_count = @part.assemblies.size
4.4.1.12 collection.find(...)
collection.find
方法在集合的表中查找对象。
@assembly = @part.assemblies.find(1)
4.4.1.13 collection.where(...)
collection.where
方法根据提供的条件在集合中查找对象,但是对象是惰性加载的,这意味着只有在访问对象时才会查询数据库。
@new_assemblies = @part.assemblies.where("created_at > ?", 2.days.ago)
4.4.1.14 collection.exists?(...)
collection.exists?
方法检查集合的表中是否存在满足提供条件的对象。
4.4.1.15 collection.build(attributes = {})
collection.build
方法返回关联类型的新对象。该对象将从传递的属性实例化,并创建通过连接表的链接,但关联对象尚未保存。
@assembly = @part.assemblies.build({ assembly_name: "Transmission housing" })
4.4.1.16 collection.create(attributes = {})
collection.create
方法返回关联类型的新对象。该对象将从传递的属性实例化,并创建通过连接表的链接,并且一旦通过关联模型上指定的所有验证,关联对象将被保存。
@assembly = @part.assemblies.create({ assembly_name: "Transmission housing" })
4.4.1.17 collection.create!(attributes = {})
与 collection.create
相同,但如果记录无效,则引发 ActiveRecord::RecordInvalid
。
4.4.1.18 collection.reload
collection.reload
方法返回所有关联对象的关系,强制进行数据库读取。如果没有关联对象,则返回一个空的关系。
@assemblies = @part.assemblies.reload
4.4.2 has_and_belongs_to_many
的选项
虽然 Rails 使用智能默认值,在大多数情况下都能很好地工作,但有时您可能希望自定义 has_and_belongs_to_many
关联引用的行为。通过在创建关联时传递选项,可以轻松实现这些自定义。例如,此关联使用了两个选项:
class Parts < ApplicationRecord
has_and_belongs_to_many :assemblies, -> { readonly },
autosave: true
end
has_and_belongs_to_many
关联支持以下选项:
:association_foreign_key
:autosave
:class_name
:foreign_key
:join_table
:validate
4.4.2.1 :association_foreign_key
按照约定,Rails 假设连接表中用于保存指向其他模型的外键的列是该模型的名称加上后缀 _id
。association_foreign_key
选项允许您直接设置外键的名称:
提示:在设置多对多自连接时,foreign_key
和 association_foreign_key
选项非常有用。例如:
class User < ApplicationRecord
has_and_belongs_to_many :friends,
class_name: "User",
foreign_key: "this_user_id",
association_foreign_key: "other_user_id"
end
4.4.2.2 :autosave
如果将 :autosave
选项设置为 true
,Rails 将保存任何加载的关联成员,并销毁标记为销毁的成员,每当保存父对象时。将 :autosave
设置为 false
不等同于不设置 :autosave
选项。如果不存在 :autosave
选项,则新的关联对象将被保存,但更新的关联对象将不会被保存。
4.4.2.3 :class_name
如果无法从关联名称推导出其他模型的名称,可以使用 :class_name
选项提供模型名称。例如,如果一个零件有多个组装件,但包含组装件的实际模型的名称是 Gadget
,则可以这样设置:
class Parts < ApplicationRecord
has_and_belongs_to_many :assemblies, class_name: "Gadget"
end
4.4.2.4 :foreign_key
按照约定,Rails 假设连接表中用于保存指向此模型的外键的列是该模型的名称加上后缀 _id
。foreign_key
选项允许您直接设置外键的名称:
class User < ApplicationRecord
has_and_belongs_to_many :friends,
class_name: "User",
foreign_key: "this_user_id",
association_foreign_key: "other_user_id"
end
4.4.2.5 :join_table
如果基于词法顺序的默认连接表名称不是您想要的名称,可以使用 :join_table
选项覆盖默认值。
4.4.2.6 :validate
如果将 :validate
选项设置为 false
,那么每当保存此对象时,新的关联对象将不会被验证。默认情况下,这是 true
:当保存此对象时,新的关联对象将被验证。
4.4.3 has_and_belongs_to_many
的作用域
有时您可能希望自定义 has_and_belongs_to_many
使用的查询。可以通过作用域块来实现这样的自定义。例如:
class Parts < ApplicationRecord
has_and_belongs_to_many :assemblies, -> { where active: true }
end
您可以在作用域块中使用任何标准的查询方法。下面讨论了以下方法:
where
extending
group
includes
limit
offset
order
readonly
select
distinct
4.4.3.1 where
where
方法允许您指定关联对象必须满足的条件。
class Parts < ApplicationRecord
has_and_belongs_to_many :assemblies,
-> { where "factory = 'Seattle'" }
end
您还可以通过哈希设置条件:
class Parts < ApplicationRecord
has_and_belongs_to_many :assemblies,
-> { where factory: 'Seattle' }
end
如果使用哈希样式的 where
,则通过此关联创建记录将自动使用哈希进行作用域限定。在这种情况下,使用 @parts.assemblies.create
或 @parts.assemblies.build
将创建 factory
列的值为 "Seattle" 的组装件。
4.4.3.2 extending
extending
方法指定要扩展关联代理的命名模块。关联扩展在本指南的后面详细讨论。
4.4.3.3 group
group
方法提供一个属性名称,用于使用 GROUP BY
子句在查找器 SQL 中对结果集进行分组。
class Parts < ApplicationRecord
has_and_belongs_to_many :assemblies, -> { group "factory" }
end
4.4.3.4 includes
您可以使用 includes
方法指定在使用此关联时应预加载的二级关联。
4.4.3.5 limit
limit
方法允许您限制通过关联获取的对象的总数。
class Parts < ApplicationRecord
has_and_belongs_to_many :assemblies,
-> { order("created_at DESC").limit(50) }
end
4.4.3.6 offset
offset
方法允许您指定通过关联获取对象的起始偏移量。例如,如果设置 offset(11)
,它将跳过前11条记录。
4.4.3.7 order
order
方法指定将接收到的关联对象的顺序(使用 SQL ORDER BY
子句使用的语法)。
class Parts < ApplicationRecord
has_and_belongs_to_many :assemblies,
-> { order "assembly_name ASC" }
end
4.4.3.8 readonly
如果使用 readonly
方法,则在通过关联检索时,关联对象将为只读。
4.4.3.9 select
select
方法允许您覆盖用于检索关联对象的 SQL SELECT
子句。默认情况下,Rails 检索所有列。
4.4.3.10 distinct
使用 distinct
方法从集合中删除重复项。
4.4.4 何时保存对象?
当将对象分配给 has_and_belongs_to_many
关联时,该对象会自动保存(以更新连接表)。如果在一个语句中分配多个对象,则它们都会保存。
如果由于验证错误而导致其中任何一个保存失败,则赋值语句将返回 false
,并且赋值本身将被取消。
如果父对象(声明 has_and_belongs_to_many
关联的对象)未保存(即 new_record?
返回 true
),则在添加它们时不会保存子对象。当保存父对象时,所有未保存的关联成员将自动保存。
如果要将对象分配给 has_and_belongs_to_many
关联而不保存对象,请使用 collection.build
方法。
4.5 关联回调
普通回调钩子进入 Active Record 对象的生命周期,允许您在各个点上使用这些对象。例如,您可以使用 :before_save
回调在对象保存之前触发某些操作。
关联回调类似于普通回调,但它们是由集合的生命周期中的事件触发的。有四个可用的关联回调:
before_add
after_add
before_remove
after_remove
您可以通过向关联声明添加选项来定义关联回调。例如:
class Author < ApplicationRecord
has_many :books, before_add: :check_credit_limit
def check_credit_limit(book)
# ...
end
end
Rails将被添加或删除的对象传递给回调函数。 您可以将回调堆叠在单个事件上,通过将它们作为数组传递:
class Author < ApplicationRecord
has_many :books,
before_add: [:check_credit_limit, :calculate_shipping_charges]
def check_credit_limit(book)
# ...
end
def calculate_shipping_charges(book)
# ...
end
end
如果before_add
回调抛出:abort
,对象将不会被添加到集合中。同样,如果before_remove
回调抛出:abort
,对象将不会从集合中移除:
# 如果达到了限制,书籍将不会被添加
def check_credit_limit(book)
throw(:abort) if limit_reached?
end
注意:这些回调仅在通过关联集合添加或删除关联对象时调用:
# 触发`before_add`回调
author.books << book
author.books = [book, book2]
# 不触发`before_add`回调
book.update(author_id: 1)
4.6 关联扩展
您不仅限于Rails自动构建到关联代理对象中的功能。您还可以通过匿名模块扩展这些对象,添加新的查找器、创建器或其他方法。例如:
class Author < ApplicationRecord
has_many :books do
def find_by_book_prefix(book_number)
find_by(category_id: book_number[0..2])
end
end
end
如果您有一个应该由许多关联共享的扩展,您可以使用一个命名扩展模块。例如:
module FindRecentExtension
def find_recent
where("created_at > ?", 5.days.ago)
end
end
class Author < ApplicationRecord
has_many :books, -> { extending FindRecentExtension }
end
class Supplier < ApplicationRecord
has_many :deliveries, -> { extending FindRecentExtension }
end
扩展可以使用proxy_association
访问器的这三个属性引用关联代理的内部:
proxy_association.owner
返回关联所属的对象。proxy_association.reflection
返回描述关联的反射对象。proxy_association.target
返回belongs_to
或has_one
的关联对象,或者返回has_many
或has_and_belongs_to_many
的关联对象集合。
4.7 使用关联所有者进行关联范围限定
在需要对关联范围进行更多控制的情况下,可以将关联所有者作为单个参数传递给作用域块。但是,请注意,将不再能够预加载关联。
class Supplier < ApplicationRecord
has_one :account, ->(supplier) { where active: supplier.active? }
end
5 单表继承(STI)
有时,您可能希望在不同的模型之间共享字段和行为。假设我们有Car、Motorcycle和Bicycle模型。我们希望为它们所有的模型共享color
和price
字段以及一些方法,但是对于每个模型都有一些特定的行为和分离的控制器。
首先,让我们生成基础的Vehicle模型:
$ bin/rails generate model vehicle type:string color:string price:decimal{10.2}
您是否注意到我们添加了一个"type"字段?由于所有模型将保存在单个数据库表中,Rails将在此列中保存正在保存的模型的名称。在我们的示例中,可以是"Car"、"Motorcycle"或"Bicycle"。如果表中没有"type"字段,STI将无法工作。
接下来,我们将生成从Vehicle继承的Car模型。为此,我们可以使用--parent=PARENT
选项,它将生成一个从指定父类继承且没有相应迁移的模型(因为表已经存在)。
例如,要生成Car模型:
$ bin/rails generate model car --parent=Vehicle
生成的模型将如下所示:
class Car < Vehicle
end
这意味着Vehicle添加的所有行为对于Car也是可用的,例如关联、公共方法等。
创建一辆汽车将在vehicles
表中保存它,并将"Car"作为type
字段:
Car.create(color: 'Red', price: 10000)
将生成以下SQL:
INSERT INTO "vehicles" ("type", "color", "price") VALUES ('Car', 'Red', 10000)
查询汽车记录将仅搜索车辆类型为汽车的记录:
Car.all
将运行类似于以下的查询:
SELECT "vehicles".* FROM "vehicles" WHERE "vehicles"."type" IN ('Car')
6 委托类型
单表继承(STI)
在子类和其属性之间的差异很小,并且包括您需要创建单个表的所有子类的所有属性时效果最好。
这种方法的缺点是会导致表的膨胀。因为它甚至会包括只有特定子类使用的属性。
在下面的示例中,有两个继承自相同"Entry"类的Active Record模型,该类包括subject
属性。
```ruby
Schema: entries[ id, type, subject, created_at, updated_at]
class Entry < ApplicationRecord end
class Comment < Entry end
class Message < Entry end ```
委托类型通过 delegated_type
解决了这个问题。
为了使用委托类型,我们必须以特定的方式对数据进行建模。要求如下:
- 有一个超类,它在其表中存储所有子类之间共享的属性。
- 每个子类必须继承自超类,并且将为其特定的任何其他属性拥有一个单独的表。
这样就不需要在一个表中定义意外共享在所有子类之间的属性。
为了将其应用到上面的示例中,我们需要重新生成我们的模型。
首先,让我们生成作为超类的基本 Entry
模型:
$ bin/rails generate model entry entryable_type:string entryable_id:integer
然后,我们将为委托生成新的 Message
和 Comment
模型:
$ bin/rails generate model message subject:string body:string
$ bin/rails generate model comment content:string
运行生成器后,我们应该得到以下模型:
# Schema: entries[ id, entryable_type, entryable_id, created_at, updated_at ]
class Entry < ApplicationRecord
end
# Schema: messages[ id, subject, body, created_at, updated_at ]
class Message < ApplicationRecord
end
# Schema: comments[ id, content, created_at, updated_at ]
class Comment < ApplicationRecord
end
6.1 声明 delegated_type
首先,在超类 Entry
中声明一个 delegated_type
。
class Entry < ApplicationRecord
delegated_type :entryable, types: %w[ Message Comment ], dependent: :destroy
end
entryable
参数指定用于委托的字段,并将 Message
和 Comment
作为委托类包含其中。
Entry
类有 entryable_type
和 entryable_id
字段。这是在 delegated_type
定义中将名称 entryable
添加了 _type
、_id
后缀的字段。
entryable_type
存储委托对象的子类名称,entryable_id
存储委托对象子类的记录 id。
接下来,我们必须定义一个模块来实现这些委托类型,通过在 has_one
关联中声明 as: :entryable
参数。
module Entryable
extend ActiveSupport::Concern
included do
has_one :entry, as: :entryable, touch: true
end
end
然后在子类中包含创建的模块。
class Message < ApplicationRecord
include Entryable
end
class Comment < ApplicationRecord
include Entryable
end
完成这个定义后,我们的 Entry
委托者现在提供以下方法:
方法 | 返回值 |
---|---|
Entry#entryable_class |
Message 或 Comment |
Entry#entryable_name |
"message" 或 "comment" |
Entry.messages |
Entry.where(entryable_type: "Message") |
Entry#message? |
当 entryable_type == "Message" 时返回 true |
Entry#message |
当 entryable_type == "Message" 时返回消息记录,否则返回 nil |
Entry#message_id |
当 entryable_type == "Message" 时返回 entryable_id ,否则返回 nil |
Entry.comments |
Entry.where(entryable_type: "Comment") |
Entry#comment? |
当 entryable_type == "Comment" 时返回 true |
Entry#comment |
当 entryable_type == "Comment" 时返回评论记录,否则返回 nil |
Entry#comment_id |
当 entryable_type == "Comment" 时返回 entryable_id ,否则返回 nil |
6.2 对象创建
在创建新的 Entry
对象时,我们可以同时指定 entryable
子类。
Entry.create! entryable: Message.new(subject: "hello!")
6.3 添加进一步的委托
我们可以扩展我们的 Entry
委托者,并通过定义 delegates
并使用多态性到子类来增强它。
例如,将 Entry
的 title
方法委托给其子类:
class Entry < ApplicationRecord
delegated_type :entryable, types: %w[ Message Comment ]
delegates :title, to: :entryable
end
class Message < ApplicationRecord
include Entryable
def title
subject
end
end
class Comment < ApplicationRecord
include Entryable
def title
content.truncate(20)
end
end
反馈
欢迎您帮助改进本指南的质量。
如果您发现任何拼写错误或事实错误,请贡献您的意见。 要开始,请阅读我们的 文档贡献 部分。
您还可能会发现不完整的内容或过时的内容。 请为主要内容添加任何缺失的文档。请先检查 Edge 指南,以验证问题是否已经修复或尚未修复。 请参阅 Ruby on Rails 指南准则 以了解样式和规范。
如果您发现需要修复但无法自行修复的问题,请 提交问题。
最后但同样重要的是,欢迎您在 官方 Ruby on Rails 论坛 上讨论有关 Ruby on Rails 文档的任何问题。