活动记录关联
关联是一组类宏方法,用于通过外键将对象联系在一起。它们表示诸如“项目有一个项目经理”或“项目属于一个投资组合”之类的关系。每个宏都会向类添加一些方法,这些方法根据集合或关联符号和选项哈希进行专门化。它的工作方式与 Ruby 自身的 attr*
方法非常相似。
class Project < ActiveRecord::Base
belongs_to :portfolio
has_one :project_manager
has_many :milestones
has_and_belongs_to_many :categories
end
项目类现在具有以下方法(及更多方法)来简化其关系的遍历和操作
project = Project.first
project.portfolio
project.portfolio = Portfolio.first
project.reload_portfolio
project.project_manager
project.project_manager = ProjectManager.first
project.reload_project_manager
project.milestones.empty?
project.milestones.size
project.milestones
project.milestones << Milestone.first
project.milestones.delete(Milestone.first)
project.milestones.destroy(Milestone.first)
project.milestones.find(Milestone.first.id)
project.milestones.build
project.milestones.create
project.categories.empty?
project.categories.size
project.categories
project.categories << Category.first
project.categories.delete(category1)
project.categories.destroy(category1)
警告
不要创建与 ActiveRecord::Base
的 实例方法 同名的关联。由于关联会向其模型添加一个具有该名称的方法,因此使用与 ActiveRecord::Base
提供的方法同名的关联将覆盖通过 ActiveRecord::Base
继承的方法,并破坏某些内容。例如,attributes
和 connection
将是关联名称的不良选择,因为这些名称已存在于 ActiveRecord::Base
实例方法列表中。
自动生成的方法
另请参阅下面的“实例公共方法”(来自 belongs_to
)以了解更多详细信息。
单数关联(一对一)
| | belongs_to |
generated methods | belongs_to | :polymorphic | has_one
----------------------------------+------------+--------------+---------
other | X | X | X
other=(other) | X | X | X
build_other(attributes={}) | X | | X
create_other(attributes={}) | X | | X
create_other!(attributes={}) | X | | X
reload_other | X | X | X
other_changed? | X | X |
other_previously_changed? | X | X |
集合关联(一对多/多对多)
| | | has_many
generated methods | habtm | has_many | :through
----------------------------------+-------+----------+----------
others | X | X | X
others=(other,other,...) | X | X | X
other_ids | X | X | X
other_ids=(id,id,...) | X | X | X
others<< | X | X | X
others.push | X | X | X
others.concat | X | X | X
others.build(attributes={}) | X | X | X
others.create(attributes={}) | X | X | X
others.create!(attributes={}) | X | X | X
others.size | X | X | X
others.length | X | X | X
others.count | X | X | X
others.sum(*args) | X | X | X
others.empty? | X | X | X
others.clear | X | X | X
others.delete(other,other,...) | X | X | X
others.delete_all | X | X | X
others.destroy(other,other,...) | X | X | X
others.destroy_all | X | X | X
others.find(*args) | X | X | X
others.exists? | X | X | X
others.distinct | X | X | X
others.reset | X | X | X
others.reload | X | X | X
覆盖生成的方法
关联方法在包含在模型类中的模块中生成,从而可以轻松覆盖。因此,可以使用 super
调用原始生成的方法
class Car < ActiveRecord::Base
belongs_to :owner
belongs_to :old_owner
def owner=(new_owner)
self.old_owner = self.owner
super
end
end
关联方法模块紧随生成的属性方法模块之后包含,这意味着关联将覆盖具有相同名称的属性的方法。
基数和关联
Active Record 关联可用于描述模型之间的一对一、一对多和多对多的关系。每个模型都使用关联来描述其在关系中的角色。belongs_to
关联始终在具有外键的模型中使用。
一对一
在基础中使用 has_one
,在关联模型中使用 belongs_to
。
class Employee < ActiveRecord::Base
has_one :office
end
class Office < ActiveRecord::Base
belongs_to :employee # foreign key - employee_id
end
一对多
在基础中使用 has_many
,在关联模型中使用 belongs_to
。
class Manager < ActiveRecord::Base
has_many :employees
end
class Employee < ActiveRecord::Base
belongs_to :manager # foreign key - manager_id
end
多对多
有两种方法可以构建多对多关系。
第一种方法使用带有 :through
选项和连接模型的 has_many
关联,因此有两个关联阶段。
class Assignment < ActiveRecord::Base
belongs_to :programmer # foreign key - programmer_id
belongs_to :project # foreign key - project_id
end
class Programmer < ActiveRecord::Base
has_many :assignments
has_many :projects, through: :assignments
end
class Project < ActiveRecord::Base
has_many :assignments
has_many :programmers, through: :assignments
end
对于第二种方法,在两个模型中使用 has_and_belongs_to_many
。这需要一个没有相应模型或主键的连接表。
class Programmer < ActiveRecord::Base
has_and_belongs_to_many :projects # foreign keys in the join table
end
class Project < ActiveRecord::Base
has_and_belongs_to_many :programmers # foreign keys in the join table
end
选择哪种方式构建多对多关系并不总是简单的。如果你需要将关系模型作为其自身的实体来使用,请使用 has_many
:through
。在使用旧架构或从未直接使用关系本身时,请使用 has_and_belongs_to_many
。
是 belongs_to
还是 has_one
关联?
两者都表示 1-1 关系。区别主要在于外键放置的位置,它位于声明 belongs_to
关系的类的表上。
class User < ActiveRecord::Base
# I reference an account.
belongs_to :account
end
class Account < ActiveRecord::Base
# One user references me.
has_one :user
end
这些类的表可能如下所示
CREATE TABLE users (
id bigint NOT NULL auto_increment,
account_id bigint default NULL,
name varchar default NULL,
PRIMARY KEY (id)
)
CREATE TABLE accounts (
id bigint NOT NULL auto_increment,
name varchar default NULL,
PRIMARY KEY (id)
)
未保存的对象和关联
你可以在将对象和关联保存到数据库之前对其进行操作,但你应该注意一些特殊行为,其中大部分涉及关联对象保存。
您可以在 has_one
、belongs_to
、has_many
或 has_and_belongs_to_many
关联中设置 :autosave
选项。将其设置为 true
将始终保存成员,而将其设置为 false
将从不保存成员。有关 :autosave
选项的更多详细信息,请访问 AutosaveAssociation
。
一对一关联
-
将对象分配给
has_one
关联将自动保存该对象和被替换的对象(如果存在),以便更新其外键 - 除非父对象未保存(new_record? == true
)。 -
如果其中任何一个保存失败(由于其中一个对象无效),则会引发
ActiveRecord::RecordNotSaved
异常,并且取消分配。 -
如果您希望将对象分配给
has_one
关联而不保存它,请使用#build_association
方法(如下所述)。将仍然保存被替换的对象以更新其外键。 -
将对象分配给
belongs_to
关联不会保存对象,因为外键字段属于父级。它也不会保存父级。
集合
-
将对象添加到集合(
has_many
或has_and_belongs_to_many
)将自动保存该对象,除非父对象(集合的所有者)尚未存储在数据库中。 -
如果保存添加到集合的任何对象(通过
push
或类似方式)失败,则push
返回false
。 -
如果在替换集合(通过
association=
)时保存失败,则会引发ActiveRecord::RecordNotSaved
异常,并且取消分配。 -
您可以使用
collection.build
方法(如下所述)将对象添加到集合而不自动保存它。 -
当保存父级时,集合的所有未保存(
new_record? == true
)成员将自动保存。
自定义查询
关联基于 Relation
对象构建,您可以使用 Relation
语法自定义它们。例如,添加条件
class Blog < ActiveRecord::Base
has_many :published_posts, -> { where(published: true) }, class_name: 'Post'
end
在 -> { ... }
块中,您可以使用所有通常的 Relation
方法。
访问所有者对象
在构建查询时,有时访问所有者对象很有用。所有者作为参数传递给块。例如,以下关联将找到用户生日当天发生的所有事件
class User < ActiveRecord::Base
has_many :birthday_events, ->(user) { where(starts_on: user.birthday) }, class_name: 'Event'
end
注意:无法联接或急切加载此类关联,因为这些操作在实例创建之前发生。此类关联可以预加载,但这样做会执行 N+1 查询,因为每条记录都会有不同的范围(类似于预加载多态范围)。
关联回调
类似于挂接到 Active Record 对象生命周期的普通回调,您还可以定义在将对象添加到关联集合或从关联集合中删除对象时触发的回调。
class Firm < ActiveRecord::Base
has_many :clients,
dependent: :destroy,
after_add: :congratulate_client,
after_remove: :log_after_remove
def congratulate_client(record)
# ...
end
def log_after_remove(record)
# ...
end
end
可以通过将回调作为数组传递来堆叠回调。示例
class Firm < ActiveRecord::Base
has_many :clients,
dependent: :destroy,
after_add: [:congratulate_client, -> (firm, record) { firm.log << "after_adding#{record.id}" }],
after_remove: :log_after_remove
end
可能的回调有:before_add
、after_add
、before_remove
和 after_remove
。
如果任何 before_add
回调引发异常,则该对象不会被添加到集合中。
类似地,如果任何 before_remove
回调引发异常,则该对象不会从集合中删除。
注意:要触发删除回调,您必须使用 destroy
/ destroy_all
方法。例如
-
firm.clients.destroy(client)
-
firm.clients.destroy(*clients)
-
firm.clients.destroy_all
以下类似 delete
/ delete_all
方法不会触发删除回调
-
firm.clients.delete(client)
-
firm.clients.delete(*clients)
-
firm.clients.delete_all
关联扩展
控制对关联访问的代理对象可以通过匿名模块进行扩展。这对于添加仅作为此关联一部分使用的新查找器、创建器和其他工厂类型方法特别有益。
class Account < ActiveRecord::Base
has_many :people do
def find_or_create_by_name(name)
first_name, last_name = name.split(" ", 2)
find_or_create_by(first_name: first_name, last_name: last_name)
end
end
end
person = Account.first.people.find_or_create_by_name("David Heinemeier Hansson")
person.first_name # => "David"
person.last_name # => "Heinemeier Hansson"
如果您需要在许多关联之间共享相同的扩展,则可以使用命名的扩展模块。
module FindOrCreateByNameExtension
def find_or_create_by_name(name)
first_name, last_name = name.split(" ", 2)
find_or_create_by(first_name: first_name, last_name: last_name)
end
end
class Account < ActiveRecord::Base
has_many :people, -> { extending FindOrCreateByNameExtension }
end
class Company < ActiveRecord::Base
has_many :people, -> { extending FindOrCreateByNameExtension }
end
某些扩展只能通过了解关联内部结构才能工作。扩展可以使用以下方法访问相关状态(其中 items
是关联的名称)
-
record.association(:items).owner
- 返回关联所属的对象。 -
record.association(:items).reflection
- 返回描述关联的反射对象。 -
record.association(:items).target
- 返回belongs_to
和has_one
的关联对象,或返回has_many
和has_and_belongs_to_many
的关联对象集合。
但是,在实际的扩展代码中,你无法像上面那样访问 record
。在这种情况下,你可以访问 proxy_association
。例如,record.association(:items)
和 record.items.proxy_association
将返回同一个对象,让你可以在关联扩展中进行诸如 proxy_association.owner
的调用。
关联连接模型
可以使用 :through
选项配置多对多关联,以使用显式连接模型来检索数据。这与 has_and_belongs_to_many
关联的操作类似。这样做的好处是,你可以在连接模型上添加验证、回调和额外属性。考虑以下模式
class Author < ActiveRecord::Base
has_many :authorships
has_many :books, through: :authorships
end
class Authorship < ActiveRecord::Base
belongs_to :author
belongs_to :book
end
@author = Author.first
@author.authorships.collect { |a| a.book } # selects all books that the author's authorships belong to
@author.books # selects all books by using the Authorship join model
你还可以通过连接模型上的 has_many
关联
class Firm < ActiveRecord::Base
has_many :clients
has_many :invoices, through: :clients
end
class Client < ActiveRecord::Base
belongs_to :firm
has_many :invoices
end
class Invoice < ActiveRecord::Base
belongs_to :client
end
@firm = Firm.first
@firm.clients.flat_map { |c| c.invoices } # select all invoices for all clients of the firm
@firm.invoices # selects all invoices by going through the Client join model
类似地,你还可以通过连接模型上的 has_one
关联
class Group < ActiveRecord::Base
has_many :users
has_many :avatars, through: :users
end
class User < ActiveRecord::Base
belongs_to :group
has_one :avatar
end
class Avatar < ActiveRecord::Base
belongs_to :user
end
@group = Group.first
@group.users.collect { |u| u.avatar }.compact # select all avatars for all users in the group
@group.avatars # selects all avatars by going through the User join model.
通过连接模型上的 has_one
或 has_many
关联时的一个重要注意事项是,这些关联是只读的。例如,在前面的示例中,以下操作将不起作用
@group.avatars << Avatar.new # this would work if User belonged_to Avatar rather than the other way around
@group.avatars.delete(@group.avatars.last) # so would this
设置反向
如果你在连接模型上使用 belongs_to
,最好在 belongs_to
上设置 :inverse_of
选项,这意味着以下示例将正确工作(其中 tags
是 has_many
:through
关联)
@post = Post.first
@tag = @post.tags.build name: "ruby"
@tag.save
最后一行应该保存通过记录(一个 Tagging
)。只有在设置 :inverse_of
时,这才能起作用
class Tagging < ActiveRecord::Base
belongs_to :post
belongs_to :tag, inverse_of: :taggings
end
如果你没有设置 :inverse_of
记录,关联将尽力将自身与正确的反向匹配。自动反向检测仅适用于 has_many
、has_one
和 belongs_to
关联。
关联上的:foreign_key
和:through
选项也会阻止自动找到关联的逆关联,在某些情况下自定义范围也会阻止。请参阅Active Record 关联指南中的更多详细信息。
逆关联的自动猜测使用基于类名称的启发式方法,因此它可能不适用于所有关联,尤其是那些名称非标准的关联。
您可以通过将:inverse_of
选项设置为false
来关闭逆关联的自动检测,如下所示
class Tagging < ActiveRecord::Base
belongs_to :tag, inverse_of: false
end
嵌套关联
您实际上可以使用:through
选项指定任何关联,包括本身具有:through
选项的关联。例如
class Author < ActiveRecord::Base
has_many :posts
has_many :comments, through: :posts
has_many :commenters, through: :comments
end
class Post < ActiveRecord::Base
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :commenter
end
@author = Author.first
@author.commenters # => People who commented on posts written by the author
设置此关联的等效方法如下
class Author < ActiveRecord::Base
has_many :posts
has_many :commenters, through: :posts
end
class Post < ActiveRecord::Base
has_many :comments
has_many :commenters, through: :comments
end
class Comment < ActiveRecord::Base
belongs_to :commenter
end
使用嵌套关联时,您将无法修改关联,因为没有足够的信息来了解要进行什么修改。例如,如果您尝试在上面的示例中添加一个Commenter
,则将无法知道如何设置中间Post
和Comment
对象。
多态关联
模型上的多态关联不受可以关联的模型类型的限制。相反,它们指定了has_many
关联必须遵守的接口。
class Asset < ActiveRecord::Base
belongs_to :attachable, polymorphic: true
end
class Post < ActiveRecord::Base
has_many :assets, as: :attachable # The :as option specifies the polymorphic interface to use.
end
@asset.attachable = @post
这是通过使用类型列和外键来指定关联记录来实现的。在 Asset 示例中,您需要一个attachable_id
整数列和一个attachable_type
字符串列。
将多态关联与单表继承 (STI) 结合使用有点棘手。为了使关联按预期工作,请确保将 STI 模型的基本模型存储在多态关联的类型列中。继续上面的资产示例,假设有使用 posts 表进行 STI 的访客帖子和会员帖子。在这种情况下,posts 表中必须有一个type
列。
注意:在分配attachable
时调用attachable_type=
方法。attachable
的class_name
作为String
传递。
class Asset < ActiveRecord::Base
belongs_to :attachable, polymorphic: true
def attachable_type=(class_name)
super(class_name.constantize.base_class.to_s)
end
end
class Post < ActiveRecord::Base
# because we store "Post" in attachable_type now dependent: :destroy will work
has_many :assets, as: :attachable, dependent: :destroy
end
class GuestPost < Post
end
class MemberPost < Post
end
缓存
所有方法都建立在一个简单的缓存原理之上,该原理将保留上次查询的结果,除非明确指示不保留。缓存甚至跨方法共享,以便在不怎么担心首次执行的性能的情况下更经济地使用宏添加的方法。
project.milestones # fetches milestones from the database
project.milestones.size # uses the milestone cache
project.milestones.empty? # uses the milestone cache
project.milestones.reload.size # fetches milestones from the database
project.milestones # uses the milestone cache
关联的急切加载
急切加载是一种查找特定类对象和多个命名关联的方法。这是防止可怕的 N+1 问题的最简单方法之一,其中获取 100 个帖子(每个帖子都需要显示其作者)会触发 101 个数据库查询。通过使用急切加载,查询数量将从 101 减少到 2。
class Post < ActiveRecord::Base
belongs_to :author
has_many :comments
end
考虑使用上述类的以下循环
Post.all.each do |post|
puts "Post: " + post.title
puts "Written by: " + post.author.name
puts "Last comment on: " + post.comments.first.created_on
end
要迭代这 100 个帖子,我们将生成 201 个数据库查询。让我们首先优化它以检索作者
Post.includes(:author).each do |post|
这引用了 belongs_to
关联的名称,该关联也使用了 :author
符号。在加载帖子后,find
将从每个帖子中收集 author_id
,并使用一个查询加载所有引用的作者。这样做会将查询数量从 201 减少到 102。
我们可以通过在查找器中引用这两个关联来进一步改善情况
Post.includes(:author, :comments).each do |post|
这将使用单个查询加载所有评论。这将总查询数量减少到 3。通常,查询数量将为 1 加上命名的关联数量(除非某些关联是多态的 belongs_to
- 见下文)。
要包含关联的深度层次结构,请使用哈希
Post.includes(:author, { comments: { author: :gravatar } }).each do |post|
以上代码将加载所有评论及其所有关联的作者和头像。您可以混合和匹配符号、数组和哈希的任意组合,以检索您要加载的关联。
所有这些功能都不应让您误以为您可以提取大量数据而不会造成性能损失,仅仅因为您减少了查询数量。数据库仍然需要将所有数据发送到 Active Record,并且仍然需要处理这些数据。因此,这并不是解决性能问题的万能方法,但它是一种在上述情况下减少查询数量的好方法。
由于一次只加载一个表,因此条件或顺序不能引用主表以外的表。如果是这种情况,Active Record 将回退到以前使用的基于 LEFT OUTER JOIN
的策略。例如
Post.includes([:author, :comments]).where(['comments.approved = ?', true])
这将导致一个带有以下连接的单个 SQL 查询:LEFT OUTER JOIN comments ON comments.post_id = posts.id
和 LEFT OUTER JOIN authors ON authors.id = posts.author_id
。请注意,使用这样的条件可能会产生意想不到的后果。在上述示例中,根本不会返回没有已批准评论的帖子,因为条件适用于整个 SQL 语句,而不仅仅适用于关联。
您必须消除列引用的歧义才能发生此回退,例如 order: "author.name DESC"
将起作用,但 order: "name DESC"
将不起作用。
如果您想加载所有帖子(包括没有已批准评论的帖子),那么使用 ON
编写您自己的 LEFT OUTER JOIN
查询
Post.joins("LEFT OUTER JOIN comments ON comments.post_id = posts.id AND comments.approved = '1'")
在这种情况下,通常更自然地包括一个在其上定义条件的关联
class Post < ActiveRecord::Base
has_many :approved_comments, -> { where(approved: true) }, class_name: 'Comment'
end
Post.includes(:approved_comments)
这将加载帖子并急切加载 approved_comments
关联,其中仅包含已批准的评论。
如果您急切加载具有指定 :limit
选项的关联,它将被忽略,返回所有关联对象
class Picture < ActiveRecord::Base
has_many :most_recent_comments, -> { order('id DESC').limit(10) }, class_name: 'Comment'
end
Picture.includes(:most_recent_comments).first.most_recent_comments # => returns all associated comments.
急切加载受多态关联支持。
class Address < ActiveRecord::Base
belongs_to :addressable, polymorphic: true
end
尝试急切加载可寻址模型的调用
Address.includes(:addressable)
这将执行一个查询以加载地址,并使用每个可寻址类型的一个查询加载可寻址对象。例如,如果所有可寻址对象都是 Person 或 Company 类,那么总共将执行 3 个查询。要加载的可寻址类型列表是在加载的地址的后面确定的。如果 Active Record 必须回退到急切加载的先前实现,则不支持此功能,并且会引发 ActiveRecord::EagerLoadPolymorphicError
。原因是父模型的类型是列值,因此其对应的表名不能放在该查询的 FROM
/JOIN
子句中。
表别名
如果表在联接中被多次引用,则 Active Record 使用表别名。如果表仅被引用一次,则使用标准表名。第二次,表将被别名为 #{reflection_name}_#{parent_table_name}
。对于表名的任何更多连续使用,都将附加索引。
Post.joins(:comments)
# SELECT ... FROM posts INNER JOIN comments ON ...
Post.joins(:special_comments) # STI
# SELECT ... FROM posts INNER JOIN comments ON ... AND comments.type = 'SpecialComment'
Post.joins(:comments, :special_comments) # special_comments is the reflection name, posts is the parent table name
# SELECT ... FROM posts INNER JOIN comments ON ... INNER JOIN comments special_comments_posts
充当树的示例
TreeMixin.joins(:children)
# SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
TreeMixin.joins(children: :parent)
# SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
# INNER JOIN parents_mixins ...
TreeMixin.joins(children: {parent: :children})
# SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
# INNER JOIN parents_mixins ...
# INNER JOIN mixins childrens_mixins_2
Has and Belongs to Many 联接表使用相同的想法,但添加了 _join
后缀
Post.joins(:categories)
# SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
Post.joins(categories: :posts)
# SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
# INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories
Post.joins(categories: {posts: :categories})
# SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
# INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories
# INNER JOIN categories_posts categories_posts_join INNER JOIN categories categories_posts_2
如果您希望使用 ActiveRecord::QueryMethods#joins
方法指定您自己的自定义联接,那么这些表名将优先于急切关联
Post.joins(:comments).joins("inner join comments ...")
# SELECT ... FROM posts INNER JOIN comments_posts ON ... INNER JOIN comments ...
Post.joins(:comments, :special_comments).joins("inner join comments ...")
# SELECT ... FROM posts INNER JOIN comments comments_posts ON ...
# INNER JOIN comments special_comments_posts ...
# INNER JOIN comments ...
根据特定数据库中表标识符的最大长度,表别名会自动截断。
模块
默认情况下,关联将在当前模块范围内查找对象。考虑
module MyApplication
module Business
class Firm < ActiveRecord::Base
has_many :clients
end
class Client < ActiveRecord::Base; end
end
end
当调用 Firm#clients
时,它将依次调用 MyApplication::Business::Client.find_all_by_firm_id(firm.id)
。如果您想与另一个模块范围内的类关联,可以通过指定完整的类名来完成。
module MyApplication
module Business
class Firm < ActiveRecord::Base; end
end
module Billing
class Account < ActiveRecord::Base
belongs_to :firm, class_name: "MyApplication::Business::Firm"
end
end
end
双向关联
当您指定关联时,通常在关联模型上有一个关联,该关联以相反的方式指定相同的关系。例如,对于以下模型
class Dungeon < ActiveRecord::Base
has_many :traps
has_one :evil_wizard
end
class Trap < ActiveRecord::Base
belongs_to :dungeon
end
class EvilWizard < ActiveRecord::Base
belongs_to :dungeon
end
Dungeon
上的 traps
关联和 Trap
上的 dungeon
关联是彼此的逆,而 EvilWizard
上的 dungeon
关联的逆是 Dungeon
上的 evil_wizard
关联(反之亦然)。默认情况下,Active Record 可以根据类的名称猜测关联的逆。结果如下
d = Dungeon.first
t = d.traps.first
d.object_id == t.dungeon.object_id # => true
上述示例中的 Dungeon
实例 d
和 t.dungeon
引用相同的内存中实例,因为关联与类的名称匹配。如果我们将 :inverse_of
添加到我们的模型定义中,结果将相同
class Dungeon < ActiveRecord::Base
has_many :traps, inverse_of: :dungeon
has_one :evil_wizard, inverse_of: :dungeon
end
class Trap < ActiveRecord::Base
belongs_to :dungeon, inverse_of: :traps
end
class EvilWizard < ActiveRecord::Base
belongs_to :dungeon, inverse_of: :evil_wizard
end
有关更多信息,请参阅 :inverse_of
选项的文档和 Active Record 关联指南。
从关联中删除
从属关联
has_many
、has_one
和 belongs_to
关联支持 :dependent
选项。这允许您指定在删除所有者时应删除关联的记录。
例如
class Author
has_many :posts, dependent: :destroy
end
Author.find(1).destroy # => Will destroy all of the author's posts, too
:dependent
选项可以具有不同的值,这些值指定如何执行删除。有关更多信息,请参阅不同特定关联类型的此选项的文档。如果没有给出选项,则在销毁记录时对关联记录不执行任何操作。
请注意,:dependent
是使用 Rails 的回调系统实现的,该系统通过按顺序处理回调来工作。因此,在 :dependent
选项之前或之后声明的其他回调可能会影响其执行操作。
请注意,对于 has_one
:through
关联,:dependent
选项将被忽略。
删除还是销毁?
has_many
和 has_and_belongs_to_many
关联具有 destroy
、delete
、destroy_all
和 delete_all
方法。
对于 has_and_belongs_to_many
,delete
和 destroy
是相同的:它们会导致连接表中的记录被删除。
对于 has_many
,destroy
和 destroy_all
始终会调用要删除的记录的 destroy
方法,以便运行回调。但是,delete
和 delete_all
将根据 :dependent
选项指定策略执行删除,或者如果没有给出 :dependent
选项,则它将遵循默认策略。默认策略是不执行任何操作(保留具有已设置父 ID 的外键),但对于 has_many
:through
除外,其默认策略是 delete_all
(删除连接记录,而不运行其回调)。
还有一个 clear
方法,它与 delete_all
相同,只是它返回关联而不是已删除的记录。
删除什么?
这里有一个潜在的陷阱:has_and_belongs_to_many
和 has_many
:through
关联在连接表中具有记录,以及关联的记录。因此,当我们调用其中一个删除方法时,究竟应该删除什么?
答案是,假设关联上的删除是关于删除所有者和关联对象之间的链接,而不是必须删除关联对象本身。因此,对于 has_and_belongs_to_many
和 has_many
:through
,连接记录将被删除,但关联记录不会被删除。
如果你仔细想想,这很有道理:如果你要调用 post.tags.delete(Tag.find_by(name: 'food'))
,你希望“food”标签与文章解除关联,而不是从数据库中删除标签本身。
但是,有一些示例表明此策略没有道理。例如,假设一个人有很多项目,每个项目都有很多任务。如果我们删除了此人的一项任务,我们可能不希望删除该项目。在此场景中,delete 方法实际上不起作用:它只能在关联模型上的关联是 belongs_to
时使用。在其他情况下,你应该直接对关联记录或 :through
关联执行操作。
使用常规 has_many
时,“关联记录”和“链接”之间没有区别,因此对于要删除的内容只有一个选择。
使用 has_and_belongs_to_many
和 has_many
:through
时,如果你想删除关联记录本身,你始终可以执行类似于 person.tasks.each(&:destroy)
的操作。
使用 ActiveRecord::AssociationTypeMismatch
进行类型安全检查
如果你尝试将对象分配给与推断或指定的 :class_name
不匹配的关联,你将收到 ActiveRecord::AssociationTypeMismatch
。
选项
所有关联宏都可以通过选项进行专门化。这使得比简单且可猜测的用例更复杂的情况成为可能。
实例公共方法
belongs_to(name, scope = nil, **options) 链接
指定与另一个类的单对单关联。仅当此类包含外键时,才应使用此方法。如果另一个类包含外键,则应改用 has_one
。另请参阅 ActiveRecord::Associations::ClassMethods
的概述,了解何时使用 has_one
,何时使用 belongs_to
。
将添加方法,用于检索和查询此对象持有 ID 的单个关联对象
association
是作为 name
参数传递的符号的占位符,因此 belongs_to :author
会添加 author.nil?
等内容。
association
-
返回关联对象。如果没有找到,则返回
nil
。 association=(associate)
-
分配关联对象,提取主键,并将其设置为外键。不会修改或删除现有记录。
build_association(attributes = {})
-
返回已使用
attributes
实例化并通过外键链接到此对象的关联类型的对象,但尚未保存。 create_association(attributes = {})
-
返回已使用
attributes
实例化、通过外键链接到此对象且已保存(如果通过验证)的关联类型的对象。 create_association!(attributes = {})
-
与
create_association
相同,但如果记录无效,则引发ActiveRecord::RecordInvalid
。 reload_association
-
返回关联对象,强制进行数据库读取。
reset_association
-
卸载关联对象。下次访问将从数据库中查询它。
association_changed?
-
如果已分配新的关联对象且下次保存将更新外键,则返回 true。
association_previously_changed?
-
如果上一次保存更新了关联以引用新的关联对象,则返回 true。
示例
class Post < ActiveRecord::Base
belongs_to :author
end
声明 belongs_to :author
会添加以下方法(及更多方法)
post = Post.find(7)
author = Author.find(19)
post.author # similar to Author.find(post.author_id)
post.author = author # similar to post.author_id = author.id
post.build_author # similar to post.author = Author.new
post.create_author # similar to post.author = Author.new; post.author.save; post.author
post.create_author! # similar to post.author = Author.new; post.author.save!; post.author
post.reload_author
post.reset_author
post.author_changed?
post.author_previously_changed?
范围
你可以传递第二个参数 scope
作为可调用对象(即过程或 lambda)以在访问关联对象时检索特定记录或自定义生成的查询。
范围示例
belongs_to :firm, -> { where(id: 2) }
belongs_to :user, -> { joins(:friends) }
belongs_to :level, ->(game) { where("game_level > ?", game.current_level) }
选项
声明还可以包括一个 options
哈希以专门化关联的行为。
:class_name
-
指定关联的类名。仅在无法从关联名称推断出该名称时使用它。因此,
belongs_to :author
默认链接到 Author 类,但如果实际类名为 Person,则必须使用此选项指定它。 :foreign_key
-
指定用于关联的外键。默认情况下,这会猜测为带有“_id”后缀的关联名称。因此,定义
belongs_to :person
关联的类将使用“person_id”作为默认:foreign_key
。类似地,belongs_to :favorite_person, class_name: "Person"
将使用外键“favorite_person_id”。设置
:foreign_key
选项会阻止自动检测关联的反向,因此通常最好也设置:inverse_of
选项。 :foreign_type
-
如果这是一个多态关联,请指定用于存储关联对象的类型的列。默认情况下,这将猜测为带有“_type”后缀的关联名称。因此,定义
belongs_to :taggable, polymorphic: true
关联的类将使用“taggable_type”作为默认的:foreign_type
。 :primary_key
-
指定用于关联关联对象的主键的方法。默认情况下,这是
id
。 :dependent
-
如果设置为
:destroy
,则在销毁此对象时销毁关联对象。如果设置为:delete
,则在不调用其销毁方法的情况下删除关联对象。如果设置为:destroy_async
,则计划在后台作业中销毁关联对象。不应在belongs_to
与另一个类上的has_many
关系结合使用时指定此选项,因为可能会留下孤立的记录。 :counter_cache
-
通过使用
CounterCache::ClassMethods#increment_counter
和CounterCache::ClassMethods#decrement_counter
在关联类上缓存属于对象的数目。在创建此类的对象时增加计数器缓存,在销毁它时减少计数器缓存。这需要在关联类(例如 Post 类)上使用名为#{table_name}_count
的列(例如,对于属于 Comment 类的comments_count
) - 这是在关联类上创建#{table_name}_count
的迁移(这样Post.comments_count
将返回缓存的计数,请参见下面的注释)。您还可以通过向此选项提供列名称(例如counter_cache: :my_custom_counter
)而不是true
/false
值来指定自定义计数器缓存列。注意:指定计数器缓存会使用attr_readonly
将其添加到该模型的只读属性列表中。 :polymorphic
-
通过传递
true
指定此关联是多态关联。注意:如果您启用了计数器缓存,则可能希望将计数器缓存属性添加到关联类中的attr_readonly
列表中(例如class Post; attr_readonly :comments_count; end
)。 :validate
-
当设置为
true
时,在保存父对象时验证添加到关联的新对象。默认情况下为false
。如果您希望确保在每次更新时重新验证关联对象,请使用validates_associated
。 :autosave
-
如果为 true,则在保存父对象时,始终保存关联对象或在标记为要销毁时销毁它。如果为 false,则永远不保存或销毁关联对象。默认情况下,仅当关联对象为新记录时才保存它。
请注意,
NestedAttributes::ClassMethods#accepts_nested_attributes_for
将:autosave
设置为true
。 :touch
-
如果为 true,则在保存或销毁此记录时,将触及关联对象(将
updated_at
/updated_on
属性设置为当前时间)。如果指定一个符号,则除了updated_at
/updated_on
属性外,还将使用当前时间更新该属性。请注意,触及时不会执行任何验证,并且只会执行after_touch
、after_commit
和after_rollback
回调。 :inverse_of
-
指定关联对象上
has_one
或has_many
关联的名称,该关联是此belongs_to
关联的反向关联。有关更多详细信息,请参阅ActiveRecord::Associations::ClassMethods
中关于双向关联的概述。 :optional
-
如果设置为
true
,则不会验证关联的存在性。 :required
-
如果设置为
true
,则还会验证关联的存在性。这将验证关联本身,而不是 ID。可以使用:inverse_of
来避免在验证期间进行额外的查询。注意:required
默认设置为true
,并且已弃用。如果不希望验证关联存在性,请使用optional: true
。 :default
-
提供一个可调用对象(即过程或 lambda)以指定关联应在验证之前使用特定记录进行初始化。请注意,如果记录存在,则不会执行可调用对象。
:strict_loading
-
每次通过此关联加载关联记录时,强制严格加载。
:ensuring_owner_was
-
指定要在所有者上调用的实例方法。该方法必须返回 true,才能在后台作业中删除关联记录。
:query_constraints
-
用作复合外键。定义用于查询关联对象的列列表。这是一个可选选项。默认情况下,
Rails
将尝试自动派生该值。设置该值时,Array
大小必须与关联模型的主键或query_constraints
大小匹配。
选项示例
belongs_to :firm, foreign_key: "client_of"
belongs_to :person, primary_key: "name", foreign_key: "person_name"
belongs_to :author, class_name: "Person", foreign_key: "author_id"
belongs_to :valid_coupon, ->(o) { where "discounts > ?", o.payments_count },
class_name: "Coupon", foreign_key: "coupon_id"
belongs_to :attachable, polymorphic: true
belongs_to :project, -> { readonly }
belongs_to :post, counter_cache: true
belongs_to :comment, touch: true
belongs_to :company, touch: :employees_last_updated_at
belongs_to :user, optional: true
belongs_to :account, default: -> { company.account }
belongs_to :account, strict_loading: true
belong_to :note, query_constraints: [:organization_id, :note_id]
来源:显示 | 在 GitHub 上
# File activerecord/lib/active_record/associations.rb, line 1886 def belongs_to(name, scope = nil, **options) reflection = Builder::BelongsTo.build(self, name, scope, options) Reflection.add_reflection self, name, reflection end
has_and_belongs_to_many(name, scope = nil, **options, &extension) 链接
指定与另一个类之间的多对多关系。这通过中间联接表关联两个类。除非联接表明确指定为选项,否则会使用类名的词法顺序猜测联接表。因此,Developer 和 Project 之间的联接将给出默认联接表名称“developers_projects”,因为“D”在字母顺序上在“P”之前。请注意,此优先级是使用 String
的 <
运算符计算的。这意味着,如果字符串长度不同,并且在比较到最短长度时字符串相等,则较长的字符串被认为比较短的字符串具有更高的词法优先级。例如,人们会期望表“paper_boxes”和“papers”生成联接表名称“papers_paper_boxes”,因为名称“paper_boxes”的长度,但实际上它生成联接表名称“paper_boxes_papers”。请注意此警告,并在需要时使用自定义 :join_table
选项。如果表共享一个公共前缀,它只会出现在开头一次。例如,表“catalog_categories”和“catalog_products”生成联接表名称“catalog_categories_products”。
联接表不应具有主键或与其关联的模型。你必须使用诸如以下内容的迁移手动生成联接表
class CreateDevelopersProjectsJoinTable < ActiveRecord::Migration[7.1]
def change
create_join_table :developers, :projects
end
end
为每个这些列添加索引以加快联接过程也是一个好主意。但是,在 MySQL 中,建议为这两个列添加复合索引,因为 MySQL 在查找期间只使用每个表的一个索引。
添加以下方法用于检索和查询
collection
是作为 name
参数传递的符号的占位符,因此 has_and_belongs_to_many :categories
将添加 categories.empty?
等内容。
collection
collection<<(object, ...)
-
通过在联接表中创建关联,向集合中添加一个或多个对象(
collection.push
和collection.concat
是此方法的别名)。请注意,此操作会立即触发更新 SQL,而无需等待父对象上的保存或更新调用,除非父对象是一条新记录。 collection.delete(object, ...)
-
通过从联接表中删除关联,从集合中删除一个或多个对象。这不会销毁对象。
collection.destroy(object, ...)
-
通过在联接表中的每个关联上运行 destroy,从集合中删除一个或多个对象,覆盖任何从属选项。这不会销毁对象。
collection=objects
-
通过适当删除和添加对象,替换集合的内容。
collection_singular_ids
-
返回关联对象的 id 数组。
collection_singular_ids=ids
-
通过
ids
中主键标识的对象替换集合。 collection.clear
-
从集合中删除每个对象。这不会销毁对象。
collection.empty?
-
如果没有关联对象,则返回
true
。 collection.size
-
返回关联对象的数目。
collection.find(id)
-
查找响应
id
的关联对象,并且满足它必须与此对象关联的条件。使用与ActiveRecord::FinderMethods#find
相同的规则。 collection.exists?(...)
-
检查是否存在具有给定条件的关联对象。使用与
ActiveRecord::FinderMethods#exists?
相同的规则。 collection.build(attributes = {})
-
返回集合类型的新对象,该对象已使用
attributes
实例化并通过联接表链接到此对象,但尚未保存。 collection.create(attributes = {})
-
返回集合类型的新对象,该对象已使用
attributes
实例化,通过联接表链接到此对象,并且已保存(如果它通过了验证)。 collection.reload
示例
class Developer < ActiveRecord::Base
has_and_belongs_to_many :projects
end
声明 has_and_belongs_to_many :projects
会添加以下方法(以及更多方法)
developer = Developer.find(11)
project = Project.find(9)
developer.projects
developer.projects << project
developer.projects.delete(project)
developer.projects.destroy(project)
developer.projects = [project]
developer.project_ids
developer.project_ids = [9]
developer.projects.clear
developer.projects.empty?
developer.projects.size
developer.projects.find(9)
developer.projects.exists?(9)
developer.projects.build # similar to Project.new(developer_id: 11)
developer.projects.create # similar to Project.create(developer_id: 11)
developer.projects.reload
声明可能包括一个 options
哈希,以专门化关联的行为。
范围
你可以传递第二个参数 scope
作为可调用对象(即过程或 lambda),以检索一组特定的记录或自定义生成查询,当你访问关联集合时。
范围示例
has_and_belongs_to_many :projects, -> { includes(:milestones, :manager) }
has_and_belongs_to_many :categories, ->(post) {
where("default_category = ?", post.default_category)
}
扩展
extension
参数允许你将一个块传递到 has_and_belongs_to_many
关联中。这对于添加新的查找器、创建器和其他工厂类型方法以用作关联的一部分非常有用。
扩展示例
has_and_belongs_to_many :contractors do
def find_or_create_by_name(name)
first_name, last_name = name.split(" ", 2)
find_or_create_by(first_name: first_name, last_name: last_name)
end
end
选项
:class_name
-
指定关联的类名。仅在无法从关联名称推断出类名时使用。因此,
has_and_belongs_to_many :projects
默认将链接到 Project 类,但如果实际类名为 SuperProject,则必须使用此选项指定。 :join_table
-
如果基于词法顺序的默认值不是您想要的,请指定联接表的名称。警告:如果您覆盖任一类的表名,则
table_name
方法必须声明在任何has_and_belongs_to_many
声明的下方才能起作用。 :foreign_key
-
指定用于关联的外键。默认情况下,这会猜测为小写此类的名称并加上后缀“_id”。因此,与 Project 建立
has_and_belongs_to_many
关联的 Person 类将使用“person_id”作为默认:foreign_key
。设置
:foreign_key
选项会阻止自动检测关联的反向,因此通常最好也设置:inverse_of
选项。 :association_foreign_key
-
指定在关联接收端用于关联的外键。默认情况下,这会猜测为小写关联类的名称并加上后缀“_id”。因此,如果 Person 类与 Project 建立
has_and_belongs_to_many
关联,则关联将使用“project_id”作为默认:association_foreign_key
。 :validate
-
设置为
true
时,在保存父对象时验证添加到关联的新对象。默认值为true
。如果您希望确保在每次更新时重新验证关联对象,请使用validates_associated
。 :autosave
-
如果为 true,则在保存父对象时始终保存关联对象或在标记为要销毁时销毁关联对象。如果为 false,则永远不保存或销毁关联对象。默认情况下,仅保存新记录的关联对象。
请注意,
NestedAttributes::ClassMethods#accepts_nested_attributes_for
将:autosave
设置为true
。 :strict_loading
-
每次通过此关联加载关联记录时,强制严格加载。
选项示例
has_and_belongs_to_many :projects
has_and_belongs_to_many :projects, -> { includes(:milestones, :manager) }
has_and_belongs_to_many :nations, class_name: "Country"
has_and_belongs_to_many :categories, join_table: "prods_cats"
has_and_belongs_to_many :categories, -> { readonly }
has_and_belongs_to_many :categories, strict_loading: true
来源:显示 | 在 GitHub 上
# File activerecord/lib/active_record/associations.rb, line 2067 def has_and_belongs_to_many(name, scope = nil, **options, &extension) habtm_reflection = ActiveRecord::Reflection::HasAndBelongsToManyReflection.new(name, scope, options, self) builder = Builder::HasAndBelongsToMany.new name, self, options join_model = builder.through_model const_set join_model.name, join_model private_constant join_model.name middle_reflection = builder.middle_reflection join_model Builder::HasMany.define_callbacks self, middle_reflection Reflection.add_reflection self, middle_reflection.name, middle_reflection middle_reflection.parent_reflection = habtm_reflection include Module.new { class_eval <<-RUBY, __FILE__, __LINE__ + 1 def destroy_associations association(:#{middle_reflection.name}).delete_all(:delete_all) association(:#{name}).reset super end RUBY } hm_options = {} hm_options[:through] = middle_reflection.name hm_options[:source] = join_model.right_reflection.name [:before_add, :after_add, :before_remove, :after_remove, :autosave, :validate, :join_table, :class_name, :extend, :strict_loading].each do |k| hm_options[k] = options[k] if options.key? k end has_many name, scope, **hm_options, &extension _reflections[name.to_s].parent_reflection = habtm_reflection end
has_many(name, scope = nil, **options, &extension) 链接
指定一对多关联。将添加以下方法用于检索和查询关联对象集合
collection
是作为 name
参数传递的符号的占位符,因此 has_many :clients
会添加 clients.empty?
等内容。
collection
collection<<(object, ...)
-
通过将一个或多个对象的外部键设置为该集合的主键来将它们添加到该集合。请注意,此操作会立即触发更新 SQL,而无需等待父对象上的保存或更新调用,除非父对象是新记录。这还将运行关联对象验证和回调。
collection.delete(object, ...)
-
通过将一个或多个对象的外部键设置为
NULL
来将它们从集合中移除。如果对象与dependent: :destroy
关联,则它们将被另外销毁;如果它们与dependent: :delete_all
关联,则它们将被删除。如果使用了
:through
选项,则默认情况下会删除连接记录(而不是将其置空),但你可以指定dependent: :destroy
或dependent: :nullify
来覆盖此设置。 collection.destroy(object, ...)
-
通过对每条记录运行
destroy
来从集合中移除一个或多个对象,无论任何依赖选项如何,确保运行回调。如果使用了
:through
选项,则会销毁连接记录,而不是对象本身。 collection=objects
-
通过根据需要删除和添加对象来替换集合内容。如果
:through
选项为 true,则连接模型中的回调将被触发,但销毁回调除外,因为默认情况下直接进行删除。你可以指定dependent: :destroy
或dependent: :nullify
来覆盖此设置。 collection_singular_ids
-
返回关联对象的 id 数组
collection_singular_ids=ids
-
使用
ids
中的主键替换集合。此方法加载模型并调用collection=
。请参见上文。 collection.clear
-
从集合中移除每个对象。如果关联对象与
dependent: :destroy
关联,则销毁它们;如果与dependent: :delete_all
直接关联,则直接从数据库中删除它们;否则,将它们的外部键设置为NULL
。如果:through
选项为 true,则不会在连接模型上调用任何销毁回调。连接模型将被直接删除。 collection.empty?
-
如果没有关联对象,则返回
true
。 collection.size
-
返回关联对象的数目。
collection.find(...)
-
根据与
ActiveRecord::FinderMethods#find
相同的规则查找关联对象。 collection.exists?(...)
-
检查是否存在具有给定条件的关联对象。使用与
ActiveRecord::FinderMethods#exists?
相同的规则。 collection.build(attributes = {}, ...)
-
返回一个或多个集合类型的新对象,这些对象已使用
attributes
实例化并通过外键链接到此对象,但尚未保存。 collection.create(attributes = {})
-
返回一个集合类型的新对象,该对象已使用
attributes
实例化,通过外键链接到此对象,并且已保存(如果通过了验证)。注意:这仅在基本模型已存在于数据库中时才有效,而不是在新(未保存)记录中有效! collection.create!(attributes = {})
-
与
collection.create
相同,但如果记录无效,则引发ActiveRecord::RecordInvalid
。 collection.reload
示例
class Firm < ActiveRecord::Base
has_many :clients
end
声明 has_many :clients
会添加以下方法(及更多方法)
firm = Firm.find(2)
client = Client.find(6)
firm.clients # similar to Client.where(firm_id: 2)
firm.clients << client
firm.clients.delete(client)
firm.clients.destroy(client)
firm.clients = [client]
firm.client_ids
firm.client_ids = [6]
firm.clients.clear
firm.clients.empty? # similar to firm.clients.size == 0
firm.clients.size # similar to Client.count "firm_id = 2"
firm.clients.find # similar to Client.where(firm_id: 2).find(6)
firm.clients.exists?(name: 'ACME') # similar to Client.exists?(name: 'ACME', firm_id: 2)
firm.clients.build # similar to Client.new(firm_id: 2)
firm.clients.create # similar to Client.create(firm_id: 2)
firm.clients.create! # similar to Client.create!(firm_id: 2)
firm.clients.reload
声明还可以包括一个 options
哈希以专门化关联的行为。
范围
你可以传递第二个参数 scope
作为可调用对象(即过程或 lambda),以检索一组特定的记录或自定义生成查询,当你访问关联集合时。
范围示例
has_many :comments, -> { where(author_id: 1) }
has_many :employees, -> { joins(:address) }
has_many :posts, ->(blog) { where("max_post_length > ?", blog.max_post_length) }
扩展
extension
参数允许你将一个块传递到 has_many
关联中。这对于添加新查找器、创建器和其他工厂类型的方法以用作关联的一部分非常有用。
扩展示例
has_many :employees do
def find_or_create_by_name(name)
first_name, last_name = name.split(" ", 2)
find_or_create_by(first_name: first_name, last_name: last_name)
end
end
选项
:class_name
-
指定关联的类名。仅在无法从关联名称推断出该名称时才使用它。因此,
has_many :products
默认情况下将链接到Product
类,但如果实际类名为SpecialProduct
,则必须使用此选项指定它。 :foreign_key
-
指定用于关联的外键。默认情况下,这被猜测为该类的小写名称和“_id”后缀。因此,建立
has_many
关联的 Person 类将使用“person_id”作为默认:foreign_key
。设置
:foreign_key
选项会阻止自动检测关联的反向,因此通常最好也设置:inverse_of
选项。 :foreign_type
-
如果这是多态关联,请指定用于存储关联对象的类型的列。默认情况下,这被猜测为在“as”选项上指定的多态关联的名称,后跟“_type”后缀。因此,定义
has_many :tags, as: :taggable
关联的类将使用“taggable_type”作为默认:foreign_type
。 :primary_key
-
指定用作关联主键的列的名称。默认情况下,这是
id
。 :dependent
-
控制当关联对象的拥有者被销毁时会发生什么。请注意,这些是作为回调实现的,并且 Rails 按顺序执行回调。因此,其他类似的回调可能会影响
:dependent
行为,并且:dependent
行为可能会影响其他回调。-
nil
不执行任何操作(默认)。 -
:destroy
会导致所有关联对象也被销毁。 -
:destroy_async
在后台作业中销毁所有关联对象。警告:如果关联在数据库中由外键约束支持,请勿使用此选项。外键约束操作将在删除其所有者的同一事务中发生。 -
:delete_all
会导致所有关联对象直接从数据库中删除(因此不会执行回调)。 -
:nullify
会导致外键被设置为NULL
。多态类型也会在多态关联中被置空。不会执行回调
。 -
:restrict_with_exception
会导致在存在任何关联记录时引发 ActiveRecord::DeleteRestrictionError 异常。 -
:restrict_with_error
会导致在存在任何关联对象时向所有者添加错误。
如果与
:through
选项一起使用,则关联模型上的关联必须是belongs_to
,并且被删除的记录是关联记录,而不是关联记录。如果在作用域关联上使用
dependent: :destroy
,则只会销毁作用域对象。例如,如果 Post 模型定义了has_many :comments, -> { where published: true }, dependent: :destroy
并且在帖子中调用了destroy
,则只会销毁已发布的评论。这意味着数据库中任何未发布的评论仍将包含指向现在已删除帖子的外键。 -
:counter_cache
-
此选项可用于配置自定义命名的
:counter_cache.
仅当你在belongs_to
关联中自定义了:counter_cache
的名称时才需要此选项。 :as
-
指定多态接口(请参阅
belongs_to
)。 :through
-
指定通过其执行查询的关联。这可以是任何其他类型的关联,包括其他
:through
关联。:class_name
、:primary_key
和:foreign_key
的选项被忽略,因为关联使用源反射。如果关联模型上的关联是
belongs_to
,则可以修改集合,并且:through
模型上的记录将根据需要自动创建和删除。否则,集合是只读的,因此你应该直接操作:through
关联。如果你要修改关联(而不是仅仅从中读取),那么最好在关联模型上的源关联中设置
:inverse_of
选项。这允许构建关联记录,这些记录在保存时将自动创建适当的关联模型记录。(请参阅上面的“关联模型”和“设置反向”部分。) :disable_joins
-
指定是否应跳过关联的联接。如果设置为 true,将生成两个或更多查询。请注意,在某些情况下,如果应用了 order 或 limit,则由于数据库限制,它将在内存中完成。此选项仅适用于
has_many :through
关联,因为单独的has_many
不会执行联接。 :source
-
指定
has_many
:through
查询使用的源关联名称。仅在无法从关联中推断出名称时才使用它。has_many :subscribers, through: :subscriptions
将在 Subscription 中查找:subscribers
或:subscriber
,除非给出了:source
。 :source_type
-
指定
has_many
:through
查询使用的源关联的类型,其中源关联是多态belongs_to
。 :validate
-
设置为
true
时,在保存父对象时验证添加到关联的新对象。默认值为true
。如果您希望确保在每次更新时重新验证关联对象,请使用validates_associated
。 :autosave
-
如果为 true,则在保存父对象时始终保存关联的对象或在标记为要销毁时销毁它们。如果为 false,则绝不保存或销毁关联的对象。默认情况下,仅保存新记录的关联对象。此选项作为
before_save
回调实现。由于回调按定义的顺序运行,因此可能需要在任何用户定义的before_save
回调中显式保存关联的对象。请注意,
NestedAttributes::ClassMethods#accepts_nested_attributes_for
将:autosave
设置为true
。 :inverse_of
-
指定关联对象上
belongs_to
关联的名称,该关联是此has_many
关联的反向关联。有关更多详细信息,请参阅ActiveRecord::Associations::ClassMethods
中关于双向关联的概述。 :extend
-
指定将扩展到返回的关联对象中的模块或模块数组。对于定义关联上的方法非常有用,尤其是在它们应该在多个关联对象之间共享时。
:strict_loading
-
设置为
true
时,每次通过此关联加载关联记录时,都会强制严格加载。 :ensuring_owner_was
-
指定要在所有者上调用的实例方法。该方法必须返回 true,才能在后台作业中删除关联记录。
:query_constraints
-
用作复合外键。定义用于查询关联对象的列列表。这是一个可选选项。默认情况下,
Rails
将尝试自动派生该值。设置该值时,Array
大小必须与关联模型的主键或query_constraints
大小匹配。
选项示例
has_many :comments, -> { order("posted_on") }
has_many :comments, -> { includes(:author) }
has_many :people, -> { where(deleted: false).order("name") }, class_name: "Person"
has_many :tracks, -> { order("position") }, dependent: :destroy
has_many :comments, dependent: :nullify
has_many :tags, as: :taggable
has_many :reports, -> { readonly }
has_many :subscribers, through: :subscriptions, source: :user
has_many :subscribers, through: :subscriptions, disable_joins: true
has_many :comments, strict_loading: true
has_many :comments, query_constraints: [:blog_id, :post_id]
来源: 显示 | 在 GitHub 上
# File activerecord/lib/active_record/associations.rb, line 1522 def has_many(name, scope = nil, **options, &extension) reflection = Builder::HasMany.build(self, name, scope, options, &extension) Reflection.add_reflection self, name, reflection end
has_one(name, scope = nil, **options) 链接
指定与另一个类的单对单关联。仅当另一个类包含外键时,才应使用此方法。如果当前类包含外键,则应使用 belongs_to
。另请参阅 ActiveRecord::Associations::ClassMethods
的概述,了解何时使用 has_one
以及何时使用 belongs_to
。
将添加以下用于检索和查询单个关联对象的函数
association
是作为 name
参数传递的符号的占位符,因此 has_one :manager
将添加 manager.nil?
等。
association
-
返回关联对象。如果没有找到,则返回
nil
。 association=(associate)
-
分配关联对象,提取主键,将其设置为外键,并保存关联对象。为了避免数据库不一致,在分配新关联对象时永久删除现有关联对象,即使新关联对象未保存到数据库中。
build_association(attributes = {})
-
返回已使用
attributes
实例化并通过外键链接到此对象的关联类型的对象,但尚未保存。 create_association(attributes = {})
-
返回已使用
attributes
实例化、通过外键链接到此对象且已保存(如果通过验证)的关联类型的对象。 create_association!(attributes = {})
-
与
create_association
相同,但如果记录无效,则引发ActiveRecord::RecordInvalid
。 reload_association
-
返回关联对象,强制进行数据库读取。
reset_association
-
卸载关联对象。下次访问将从数据库中查询它。
示例
class Account < ActiveRecord::Base
has_one :beneficiary
end
声明 has_one :beneficiary
会添加以下函数(以及更多函数)
account = Account.find(5)
beneficiary = Beneficiary.find(8)
account.beneficiary # similar to Beneficiary.find_by(account_id: 5)
account.beneficiary = beneficiary # similar to beneficiary.update(account_id: 5)
account.build_beneficiary # similar to Beneficiary.new(account_id: 5)
account.create_beneficiary # similar to Beneficiary.create(account_id: 5)
account.create_beneficiary! # similar to Beneficiary.create!(account_id: 5)
account.reload_beneficiary
account.reset_beneficiary
范围
你可以传递第二个参数 scope
作为可调用对象(即过程或 lambda)以在访问关联对象时检索特定记录或自定义生成的查询。
范围示例
has_one :author, -> { where(comment_id: 1) }
has_one :employer, -> { joins(:company) }
has_one :latest_post, ->(blog) { where("created_at > ?", blog.enabled_at) }
选项
声明还可以包括一个 options
哈希以专门化关联的行为。
选项为
:class_name
-
指定关联的类名。仅在无法从关联名称推断出该名称时才使用它。因此,
has_one :manager
默认将链接到 Manager 类,但如果实际类名为 Person,则必须使用此选项指定它。 :dependent
-
控制当关联对象的所有者被销毁时对关联对象执行的操作
-
nil
不执行任何操作(默认)。 -
:destroy
导致关联对象也被销毁 -
:destroy_async
导致关联对象在后台作业中被销毁。警告:如果关联是由数据库中的外键约束支持的,请不要使用此选项。外键约束操作将在删除其所有者的同一事务中发生。 -
:delete
导致关联对象直接从数据库中删除(因此回调不会执行) -
:nullify
导致外键被设置为NULL
。多态类型列在多态关联中也被置为 null。回调
不会执行。 -
:restrict_with_exception
导致在存在关联记录时引发 ActiveRecord::DeleteRestrictionError 异常 -
:restrict_with_error
导致在存在关联对象时向所有者添加错误
请注意,在使用
:through
选项时,:dependent
选项将被忽略。 -
:foreign_key
-
指定用于关联的外键。默认情况下,这被猜测为小写此类的名称和“_id”后缀。因此,建立
has_one
关联的 Person 类将使用“person_id”作为默认的:foreign_key
。设置
:foreign_key
选项会阻止自动检测关联的反向,因此通常最好也设置:inverse_of
选项。 :foreign_type
-
如果这是多态关联,则指定用于存储关联对象类型的列。默认情况下,这被猜测为在“as”选项上指定的多态关联的名称,后缀为“_type”。因此,定义
has_one :tag, as: :taggable
关联的类将使用“taggable_type”作为默认的:foreign_type
。 :primary_key
-
指定用于返回关联所用主键的方法。默认情况下,这是
id
。 :as
-
指定多态接口(请参阅
belongs_to
)。 :through
-
指定通过其执行查询的连接模型。
:class_name
、:primary_key
和:foreign_key
的选项将被忽略,因为关联使用源反射。你只能通过连接模型上的has_one
或belongs_to
关联使用:through
查询。如果关联模型上的关联是
belongs_to
,则可以修改集合,并且:through
模型上的记录将根据需要自动创建和删除。否则,集合是只读的,因此你应该直接操作:through
关联。如果你要修改关联(而不是仅仅从中读取),那么最好在关联模型上的源关联中设置
:inverse_of
选项。这允许构建关联记录,这些记录在保存时将自动创建适当的关联模型记录。(请参阅上面的“关联模型”和“设置反向”部分。) :disable_joins
-
指定是否应跳过关联的连接。如果设置为 true,将生成两个或更多查询。请注意,在某些情况下,如果应用了 order 或 limit,则由于数据库限制,它将在内存中完成。此选项仅适用于
has_one :through
关联,因为has_one
本身不执行连接。 :source
-
指定
has_one
:through
查询所用的源关联名称。仅在无法从关联推断出名称时才使用它。has_one :favorite, through: :favorites
将在 Favorite 上查找:favorite
,除非给定:source
。 :source_type
-
指定
has_one
:through
查询所用源关联的类型,其中源关联是多态belongs_to
。 :validate
-
当设置为
true
时,在保存父对象时验证添加到关联的新对象。默认情况下为false
。如果您希望确保在每次更新时重新验证关联对象,请使用validates_associated
。 :autosave
-
如果为 true,则在保存父对象时,始终保存关联对象或在标记为要销毁时销毁它。如果为 false,则永远不保存或销毁关联对象。默认情况下,仅当关联对象为新记录时才保存它。
请注意,
NestedAttributes::ClassMethods#accepts_nested_attributes_for
将:autosave
设置为true
。 :touch
-
如果为 true,则在保存或销毁此记录时,将触及关联对象(将
updated_at
/updated_on
属性设置为当前时间)。如果指定一个符号,则除了updated_at
/updated_on
属性外,还将使用当前时间更新该属性。请注意,触及时不会执行任何验证,并且只会执行after_touch
、after_commit
和after_rollback
回调。 :inverse_of
-
指定关联对象上
belongs_to
关联的名称,该关联是此has_one
关联的反向关联。有关更多详细信息,请参阅ActiveRecord::Associations::ClassMethods
中关于双向关联的概述。 :required
-
当设置为
true
时,关联还将对其存在性进行验证。这将验证关联本身,而不是 id。你可以使用:inverse_of
在验证期间避免额外的查询。 :strict_loading
-
每次通过此关联加载关联记录时,强制严格加载。
:ensuring_owner_was
-
指定要在所有者上调用的实例方法。该方法必须返回 true,才能在后台作业中删除关联记录。
:query_constraints
-
用作复合外键。定义用于查询关联对象的列列表。这是一个可选选项。默认情况下,
Rails
将尝试自动派生该值。设置该值时,Array
大小必须与关联模型的主键或query_constraints
大小匹配。
选项示例
has_one :credit_card, dependent: :destroy # destroys the associated credit card
has_one :credit_card, dependent: :nullify # updates the associated records foreign
# key value to NULL rather than destroying it
has_one :last_comment, -> { order('posted_on') }, class_name: "Comment"
has_one :project_manager, -> { where(role: 'project_manager') }, class_name: "Person"
has_one :attachment, as: :attachable
has_one :boss, -> { readonly }
has_one :club, through: :membership
has_one :club, through: :membership, disable_joins: true
has_one :primary_address, -> { where(primary: true) }, through: :addressables, source: :addressable
has_one :credit_card, required: true
has_one :credit_card, strict_loading: true
has_one :employment_record_book, query_constraints: [:organization_id, :employee_id]
来源: 显示 | 在 GitHub 上
# File activerecord/lib/active_record/associations.rb, line 1708 def has_one(name, scope = nil, **options) reflection = Builder::HasOne.build(self, name, scope, options) Reflection.add_reflection self, name, reflection end