Active Record 关系
- #
- A
- B
- C
- D
- E
- F
- I
- J
- L
- M
- N
- O
- P
- R
- S
- T
- U
- V
常量
CLAUSE_METHODS | = | [:where, :having, :from] |
INVALID_METHODS_FOR_DELETE_ALL | = | [:distinct, :with, :with_recursive] |
MULTI_VALUE_METHODS | = | [:includes, :eager_load, :preload, :select, :group, :order, :joins, :left_outer_joins, :references, :extending, :unscope, :optimizer_hints, :annotate, :with] |
SINGLE_VALUE_METHODS | = | [:limit, :offset, :lock, :readonly, :reordering, :strict_loading, :reverse_order, :distinct, :create_with, :skip_query_cache] |
VALUE_METHODS | = | MULTI_VALUE_METHODS + SINGLE_VALUE_METHODS + CLAUSE_METHODS |
属性
[R] | klass | |
[R] | loaded | |
[R] | loaded? | |
[R] | model | |
[R] | predicate_builder | |
[RW] | skip_preloading_value | |
[R] | table |
类公共方法
new(model, table: nil, predicate_builder: nil, values: {}) 链接
来源: 显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation.rb, line 77 def initialize(model, table: nil, predicate_builder: nil, values: {}) if table predicate_builder ||= model.predicate_builder.with(TableMetadata.new(model, table)) else table = model.arel_table predicate_builder ||= model.predicate_builder end @model = model @table = table @values = values @loaded = false @predicate_builder = predicate_builder @delegate_to_model = false @future_result = nil @records = nil @async = false @none = false end
实例公共方法
==(other) 链接
比较两个关系以判断是否相等。
来源: 显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation.rb, line 1253 def ==(other) case other when Associations::CollectionProxy, AssociationRelation self == other.records when Relation other.to_sql == to_sql when Array records == other end end
any?(*args) 链接
如果存在任何记录,则返回 true。
当给出模式参数时,此方法通过 case-equality 运算符 (===
) 检查 Enumerable
中的元素是否与模式匹配。
posts.any?(Post) # => true or false
来源: 显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation.rb, line 391 def any?(*args) return false if @none return super if args.present? || block_given? !empty? end
blank?() 链接
如果关系为空,则返回 true。
来源: 显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation.rb, line 1274 def blank? records.blank? end
cache_key(timestamp_column = "updated_at") 链接
返回一个稳定的缓存键,可用于标识此查询。缓存键是使用 SQL 查询的指纹构建的。
Product.where("name like ?", "%Cosmic Encounter%").cache_key
# => "products/query-1850ab3d302391b85b8693e941286659"
如果 ActiveRecord::Base.collection_cache_versioning
被关闭(如 Rails 6.0 及更早版本中那样),缓存键还将包含一个版本。
ActiveRecord::Base.collection_cache_versioning = false
Product.where("name like ?", "%Cosmic Encounter%").cache_key
# => "products/query-1850ab3d302391b85b8693e941286659-1-20150714212553907087000"
您也可以传递自定义时间戳列来获取最新更新记录的时间戳。
Product.where("name like ?", "%Game%").cache_key(:last_reviewed_at)
来源: 显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation.rb, line 438 def cache_key(timestamp_column = "updated_at") @cache_keys ||= {} @cache_keys[timestamp_column] ||= model.collection_cache_key(self, timestamp_column) end
cache_key_with_version() 链接
返回缓存键和版本。
来源: 显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation.rb, line 519 def cache_key_with_version if version = cache_version "#{cache_key}-#{version}" else cache_key end end
cache_version(timestamp_column = :updated_at) 链接
返回一个缓存版本,可与缓存键一起使用以形成可回收的缓存方案。缓存版本是使用匹配查询的记录数量和最新更新记录的时间戳构建的。当新记录匹配查询或任何现有记录被更新或删除时,缓存版本会更改。
如果集合已加载,则该方法将遍历记录以生成时间戳,否则它将触发一个 SQL 查询,例如
SELECT COUNT(*), MAX("products"."updated_at") FROM "products" WHERE (name like '%Cosmic Encounter%')
来源: 显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation.rb, line 465 def cache_version(timestamp_column = :updated_at) if model.collection_cache_versioning @cache_versions ||= {} @cache_versions[timestamp_column] ||= compute_cache_version(timestamp_column) end end
create(attributes = nil, &block) 链接
尝试使用关系中定义的相同作用域属性创建一个新记录。如果验证失败,则返回已初始化的对象。
期望参数与 ActiveRecord::Base.create 中的格式相同。
示例
users = User.where(name: 'Oscar')
users.create # => #<User id: 3, name: "Oscar", ...>
users.create(name: 'fxn')
users.create # => #<User id: 4, name: "fxn", ...>
users.create { |user| user.name = 'tenderlove' }
# => #<User id: 5, name: "tenderlove", ...>
users.create(name: nil) # validation on name
# => #<User id: nil, name: nil, ...>
来源: 显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation.rb, line 154 def create(attributes = nil, &block) if attributes.is_a?(Array) attributes.collect { |attr| create(attr, &block) } else block = current_scope_restoring_block(&block) scoping { _create(attributes, &block) } end end
create!(attributes = nil, &block) 链接
与 create
类似,但调用基类上的 create!。如果验证错误,则引发异常。
期望参数与 ActiveRecord::Base.create! 中的格式相同。
来源: 显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation.rb, line 169 def create!(attributes = nil, &block) if attributes.is_a?(Array) attributes.collect { |attr| create!(attr, &block) } else block = current_scope_restoring_block(&block) scoping { _create!(attributes, &block) } end end
create_or_find_by(attributes, &block) 链接
尝试在具有一个或多个列上的唯一数据库约束的表中使用给定属性创建记录。如果已存在具有这些唯一约束之一或多个的行,则通常会引发插入的异常会被捕获,并且使用 find_by! 找到具有这些属性的现有记录。
这与 find_or_create_by
类似,但首先尝试创建记录。因此,它更适合记录很可能尚未存在的情况。
但是,create_or_find_by
存在一些缺点。
-
基础表必须具有使用唯一数据库约束定义的相关列。
-
唯一约束违反可能仅由一个或至少少于所有给定属性触发。这意味着随后的 find_by! 可能会无法找到匹配的记录,这将引发
ActiveRecord::RecordNotFound
异常,而不是具有给定属性的记录。 -
虽然我们避免了
find_or_create_by
中的 SELECT -> INSERT 之间的竞争条件,但实际上我们还有 INSERT -> SELECT 之间的另一个竞争条件,如果另一个客户端运行这两个语句之间的 DELETE,则可能会触发该条件。但对于大多数应用程序来说,这是一个不太可能发生的条件。 -
它依赖于异常处理来处理控制流,这可能会稍微慢一些。
-
主键可能会在每次创建时自动递增,即使创建失败。如果基础表仍然停留在 int 类型的主键上,这会加剧整数耗尽的问题(注意:从 5.1+ 开始的所有 Rails 应用程序默认使用 bigint,这不会出现此问题)。
-
具有唯一数据库约束的列不应定义唯一性验证,否则
create
将因验证错误而失败,并且永远不会调用 find_by。
如果所有给定属性都包含在唯一约束中,此方法将返回一个记录(除非触发 INSERT -> DELETE -> SELECT 竞争条件),但如果创建尝试失败并且因验证错误而失败,则它将不会被持久化,您将获得 create
在这种情况下返回的值。
来源: 显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation.rb, line 273 def create_or_find_by(attributes, &block) with_connection do |connection| transaction(requires_new: true) { create(attributes, &block) } rescue ActiveRecord::RecordNotUnique if connection.transaction_open? where(attributes).lock.find_by!(attributes) else find_by!(attributes) end end end
create_or_find_by!(attributes, &block) 链接
类似于 create_or_find_by
,但调用 create!,因此如果创建的记录无效,则会引发异常。
来源:显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation.rb, line 288 def create_or_find_by!(attributes, &block) with_connection do |connection| transaction(requires_new: true) { create!(attributes, &block) } rescue ActiveRecord::RecordNotUnique if connection.transaction_open? where(attributes).lock.find_by!(attributes) else find_by!(attributes) end end end
delete(id_or_array) 链接
使用 SQL DELETE
语句删除主键与 id
参数匹配的行,并返回删除的行数。不会实例化 Active Record 对象,因此不会执行对象的回调,包括任何 :dependent
关联选项。
可以通过传递 id
的 Array
来一次删除多行。
注意:虽然这通常比替代方案 destroy
快得多,但跳过回调可能会绕过应用程序中确保引用完整性或执行其他基本任务的业务逻辑。
示例
# Delete a single row
Todo.delete(1)
# Delete multiple rows
Todo.delete([2,3,4])
来源:显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation.rb, line 1057 def delete(id_or_array) return 0 if id_or_array.nil? || (id_or_array.is_a?(Array) && id_or_array.empty?) where(model.primary_key => id_or_array).delete_all end
delete_all() 链接
删除记录,不会先实例化记录,因此不会调用 #destroy 方法也不会调用回调。这是一个直接进入数据库的单个 SQL DELETE 语句,比 destroy_all
更有效率。但要小心关系,尤其是关联上定义的 :dependent
规则不会被执行。返回受影响的行数。
Post.where(person_id: 5).where(category: ['Something', 'Else']).delete_all
这两个调用都会使用单个 DELETE 语句立即删除受影响的帖子。如果需要销毁依赖关联或调用 before_*
或 after_destroy
回调,请改用 destroy_all
方法。
如果提供无效方法,delete_all
会引发 ActiveRecordError
Post.distinct.delete_all
# => ActiveRecord::ActiveRecordError: delete_all doesn't support distinct
来源:显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation.rb, line 1011 def delete_all return 0 if @none invalid_methods = INVALID_METHODS_FOR_DELETE_ALL.select do |method| value = @values[method] method == :distinct ? value : value&.any? end if invalid_methods.any? raise ActiveRecordError.new("delete_all doesn't support #{invalid_methods.join(', ')}") end model.with_connection do |c| arel = eager_loading? ? apply_join_dependency.arel : build_arel(c) arel.source.left = table group_values_arel_columns = arel_columns(group_values.uniq) having_clause_ast = having_clause.ast unless having_clause.empty? key = if model.composite_primary_key? primary_key.map { |pk| table[pk] } else table[primary_key] end stmt = arel.compile_delete(key, having_clause_ast, group_values_arel_columns) c.delete(stmt, "#{model} Delete All").tap { reset } end end
delete_by(*args) 链接
查找并删除与指定条件匹配的所有记录。这是 relation.where(condition).delete_all
的简写。返回受影响的行数。
如果未找到任何记录,则返回 0
,因为零行受影响。
Person.delete_by(id: 13)
Person.delete_by(name: 'Spartacus', rating: 4)
Person.delete_by("published_at < ?", 2.weeks.ago)
来源:显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation.rb, line 1119 def delete_by(*args) where(*args).delete_all end
destroy(id) 链接
销毁具有给定 id 的对象(或多个对象)。该对象首先被实例化,因此所有回调和过滤器都在对象被删除之前被触发。这种方法不如 delete
高效,但允许运行清理方法和其他操作。
这实质上是找到具有给定 id 的对象(或多个对象),从属性创建新对象,然后调用 destroy 方法。
参数
-
id
- 应该是要销毁的 id 或 id 数组。
示例
# Destroy a single object
Todo.destroy(1)
# Destroy multiple objects
todos = [1,2,3]
Todo.destroy(todos)
来源:显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation.rb, line 1083 def destroy(id) multiple_ids = if model.composite_primary_key? id.first.is_a?(Array) else id.is_a?(Array) end if multiple_ids find(id).each(&:destroy) else find(id).destroy end end
destroy_all() 链接
通过实例化每个记录并调用其 #destroy 方法来销毁记录。执行每个对象的回调(包括 :dependent
关联选项)。返回已销毁的对象集合;每个对象都将被冻结,以反映不应进行任何更改(因为它们无法持久化)。
注意:当您一次删除许多记录时,每个记录的实例化、回调执行和删除可能会非常耗时。它会为每个记录生成至少一个 SQL DELETE
查询(或者可能更多,以强制执行回调)。如果您想快速删除许多行,而不关心它们的关联或回调,请改用 delete_all
。
示例
Person.where(age: 0..18).destroy_all
来源:显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation.rb, line 989 def destroy_all records.each(&:destroy).tap { reset } end
destroy_by(*args) 链接
查找并销毁与指定条件匹配的所有记录。这是 relation.where(condition).destroy_all
的简写。返回已销毁的对象集合。
如果未找到任何记录,则返回空数组。
Person.destroy_by(id: 13)
Person.destroy_by(name: 'Spartacus', rating: 4)
Person.destroy_by("published_at < ?", 2.weeks.ago)
来源:显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation.rb, line 1106 def destroy_by(*args) where(*args).destroy_all end
eager_loading?() 链接
如果关系需要急切加载,则返回 true。
来源:显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation.rb, line 1238 def eager_loading? @should_eager_load ||= eager_load_values.any? || includes_values.any? && (joined_includes_values.any? || references_eager_loaded_tables?) end
empty?() 链接
如果没有任何记录,则返回 true。
来源:显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation.rb, line 362 def empty? return true if @none if loaded? records.empty? else !exists? end end
encode_with(coder) 链接
序列化关系对象 Array
。
来源:显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation.rb, line 348 def encode_with(coder) coder.represent_seq(nil, records) end
explain(*options) 链接
对该关系触发的查询或查询运行 EXPLAIN,并将结果作为字符串返回。该字符串的格式模仿数据库 shell 中打印的字符串。
User.all.explain
# EXPLAIN SELECT `users`.* FROM `users`
# ...
注意,此方法实际上会运行查询,因为急切加载正在进行时,一些查询的结果需要由下一个查询使用。
要在由 first
、pluck
和 count
创建的查询上运行 EXPLAIN,请在 explain
上调用这些方法。
User.all.explain.count
# EXPLAIN SELECT COUNT(*) FROM `users`
# ...
如果需要,可以传递列名。
User.all.explain.maximum(:id)
# EXPLAIN SELECT MAX(`users`.`id`) FROM `users`
# ...
请在 Active Record 查询接口指南 中查看更多详细信息。
来源:显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation.rb, line 332 def explain(*options) ExplainProxy.new(self, options) end
find_or_create_by(attributes, &block) 链接
查找具有给定属性的第一条记录,如果未找到记录,则使用该属性创建一条记录。
# Find the first user named "Penélope" or create a new one.
User.find_or_create_by(first_name: 'Penélope')
# => #<User id: 1, first_name: "Penélope", last_name: nil>
# Find the first user named "Penélope" or create a new one.
# We already have one so the existing record will be returned.
User.find_or_create_by(first_name: 'Penélope')
# => #<User id: 1, first_name: "Penélope", last_name: nil>
# Find the first user named "Scarlett" or create a new one with
# a particular last name.
User.create_with(last_name: 'Johansson').find_or_create_by(first_name: 'Scarlett')
# => #<User id: 2, first_name: "Scarlett", last_name: "Johansson">
此方法接受一个块,该块将传递到 create
。上面的最后一个示例可以这样改写
# Find the first user named "Scarlett" or create a new one with a
# particular last name.
User.find_or_create_by(first_name: 'Scarlett') do |user|
user.last_name = 'Johansson'
end
# => #<User id: 2, first_name: "Scarlett", last_name: "Johansson">
此方法始终返回记录,但如果由于验证错误导致创建尝试失败,则不会持久化,您将获得 create
在这种情况下返回的内容。
如果由于唯一约束导致创建失败,此方法将假定遇到竞争条件,并将再次尝试查找记录。如果由于并发 DELETE 发生导致第二次查找仍然找不到记录,则会引发 ActiveRecord::RecordNotFound
异常。
请注意,此方法不是原子性的,它首先运行 SELECT,如果没有任何结果,则尝试 INSERT。因此,如果表没有相关的唯一约束,则您最终可能会得到两个或更多类似记录。
来源:显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation.rb, line 231 def find_or_create_by(attributes, &block) find_by(attributes) || create_or_find_by(attributes, &block) end
find_or_create_by!(attributes, &block) 链接
类似于 find_or_create_by
,但调用 create!,因此如果创建的记录无效,则会引发异常。
来源:显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation.rb, line 238 def find_or_create_by!(attributes, &block) find_by(attributes) || create_or_find_by!(attributes, &block) end
find_or_initialize_by(attributes, &block) 链接
类似于 find_or_create_by
,但调用 new 而不是 create。
来源:显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation.rb, line 302 def find_or_initialize_by(attributes, &block) find_by(attributes) || new(attributes, &block) end
initialize_copy(other) 链接
来源:显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation.rb, line 97 def initialize_copy(other) @values = @values.dup reset end
insert(attributes, returning: nil, unique_by: nil, record_timestamps: nil) 链接
将单个记录插入到数据库中,在一个单个 SQL INSERT 语句中。它不会实例化任何模型,也不会触发 Active Record 回调或验证。虽然传递的值会经过 Active Record 的类型转换和序列化。
有关文档,请参见 insert_all
。
来源:显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation.rb, line 644 def insert(attributes, returning: nil, unique_by: nil, record_timestamps: nil) insert_all([ attributes ], returning: returning, unique_by: unique_by, record_timestamps: record_timestamps) end
insert!(attributes, returning: nil, record_timestamps: nil) 链接
将单个记录插入到数据库中,在一个单个 SQL INSERT 语句中。它不会实例化任何模型,也不会触发 Active Record 回调或验证。虽然传递的值会经过 Active Record 的类型转换和序列化。
有关更多信息,请参见 insert_all!
。
来源:显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation.rb, line 733 def insert!(attributes, returning: nil, record_timestamps: nil) insert_all!([ attributes ], returning: returning, record_timestamps: record_timestamps) end
insert_all(attributes, returning: nil, unique_by: nil, record_timestamps: nil) 链接
将多条记录插入到数据库中,在一个单个 SQL INSERT 语句中。它不会实例化任何模型,也不会触发 Active Record 回调或验证。虽然传递的值会经过 Active Record 的类型转换和序列化。
attributes
参数是一个 Array
的 Hashes。每个 Hash
决定了单行的属性,并且必须具有相同的键。
行被认为是通过表上的每个唯一索引来区分的。任何重复行都将被跳过。使用 :unique_by
覆盖(见下文)。
返回一个 ActiveRecord::Result
,其内容基于 :returning
(见下文)。
选项
- :returning
-
(仅限 PostgreSQL、SQLite3 和 MariaDB) 要为所有成功插入的记录返回的属性数组,默认情况下是主键。传递
returning: %w[ id name ]
来获取 id 和 name,或returning: false
来完全省略底层的RETURNING
SQL 子句。如果您需要对返回值进行更多控制,也可以传递一个 SQL 字符串(例如,
returning: Arel.sql("id, name as new_name")
)。 - :unique_by
-
(仅限 PostgreSQL 和 SQLite) 默认情况下,行被认为是通过表上的每个唯一索引来区分的。任何重复的行都会被跳过。
要根据单个唯一索引跳过行,请传递
:unique_by
。假设有一个 Book 模型,其中不应出现重复的 ISBN,但如果任何行具有现有 ID 或未被其他唯一索引区分,则会引发
ActiveRecord::RecordNotUnique
错误。唯一索引可以通过列或名称来标识
unique_by: :isbn unique_by: %i[ author_id name ] unique_by: :index_books_on_isbn
- :record_timestamps
-
默认情况下,时间戳列的自动设置由模型的
record_timestamps
配置控制,与典型行为一致。要覆盖此行为并强制以某种方式自动设置时间戳列,请传递
:record_timestamps
。record_timestamps: true # Always set timestamps automatically record_timestamps: false # Never set timestamps automatically
由于 :unique_by
依赖于数据库中的索引信息,因此建议将其与 Active Record 的 schema_cache 配合使用。
示例
# Insert records and skip inserting any duplicates.
# Here "Eloquent Ruby" is skipped because its id is not unique.
Book.insert_all([
{ id: 1, title: "Rework", author: "David" },
{ id: 1, title: "Eloquent Ruby", author: "Russ" }
])
# insert_all works on chained scopes, and you can use create_with
# to set default attributes for all inserted records.
author.books.create_with(created_at: Time.now).insert_all([
{ id: 1, title: "Rework" },
{ id: 2, title: "Eloquent Ruby" }
])
源代码:显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation.rb, line 723 def insert_all(attributes, returning: nil, unique_by: nil, record_timestamps: nil) InsertAll.execute(self, attributes, on_duplicate: :skip, returning: returning, unique_by: unique_by, record_timestamps: record_timestamps) end
insert_all!(attributes, returning: nil, record_timestamps: nil) 链接
将多条记录插入到数据库中,在一个单个 SQL INSERT 语句中。它不会实例化任何模型,也不会触发 Active Record 回调或验证。虽然传递的值会经过 Active Record 的类型转换和序列化。
attributes
参数是一个 Array
的 Hashes。每个 Hash
决定了单行的属性,并且必须具有相同的键。
如果任何行违反了表上的唯一索引,则会引发 ActiveRecord::RecordNotUnique
错误。在这种情况下,不会插入任何行。
要跳过重复的行,请参见 insert_all
。要替换它们,请参见 upsert_all
。
返回一个 ActiveRecord::Result
,其内容基于 :returning
(见下文)。
选项
- :returning
-
(仅限 PostgreSQL、SQLite3 和 MariaDB) 要为所有成功插入的记录返回的属性数组,默认情况下是主键。传递
returning: %w[ id name ]
来获取 id 和 name,或returning: false
来完全省略底层的RETURNING
SQL 子句。如果您需要对返回值进行更多控制,也可以传递一个 SQL 字符串(例如,
returning: Arel.sql("id, name as new_name")
)。 - :record_timestamps
-
默认情况下,时间戳列的自动设置由模型的
record_timestamps
配置控制,与典型行为一致。要覆盖此行为并强制以某种方式自动设置时间戳列,请传递
:record_timestamps
。record_timestamps: true # Always set timestamps automatically record_timestamps: false # Never set timestamps automatically
示例
# Insert multiple records
Book.insert_all!([
{ title: "Rework", author: "David" },
{ title: "Eloquent Ruby", author: "Russ" }
])
# Raises ActiveRecord::RecordNotUnique because "Eloquent Ruby"
# does not have a unique id.
Book.insert_all!([
{ id: 1, title: "Rework", author: "David" },
{ id: 1, title: "Eloquent Ruby", author: "Russ" }
])
源代码:显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation.rb, line 790 def insert_all!(attributes, returning: nil, record_timestamps: nil) InsertAll.execute(self, attributes, on_duplicate: :raise, returning: returning, record_timestamps: record_timestamps) end
inspect() 链接
源代码:显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation.rb, line 1290 def inspect subject = loaded? ? records : annotate("loading for inspect") entries = subject.take([limit_value, 11].compact.min).map!(&:inspect) entries[10] = "..." if entries.size == 11 "#<#{self.class.name} [#{entries.join(', ')}]>" end
joined_includes_values() 链接
同时标记为预加载的联接。在这种情况下,我们应该只急切加载它们。请注意,这是一个简单的实现,因为我们可能有表示相同关联的字符串和符号,但它们没有被此匹配。此外,我们可能拥有部分匹配的嵌套哈希,例如 { a: :b } & { a: [:b, :c] }
源代码:显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation.rb, line 1248 def joined_includes_values includes_values & joins_values end
load(&block) 链接
如果记录尚未加载,则会从数据库中加载这些记录。如果您需要在实际使用记录之前显式加载一些记录,可以使用此方法。返回值是关系本身,而不是记录。
Post.where(published: true).load # => #<ActiveRecord::Relation>
源代码:显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation.rb, line 1179 def load(&block) if !loaded? || scheduled? @records = exec_queries(&block) @loaded = true end self end
load_async() 链接
将查询安排从后台线程池执行。
Post.where(published: true).load_async # => #<ActiveRecord::Relation>
当迭代 Relation
时,如果后台查询尚未执行,它将在前台线程执行。
请注意,必须配置 config.active_record.async_query_executor 才能实际并发执行查询。否则,它将默认在前台执行查询。
如果查询实际上是在后台执行的,Active Record 日志将在日志行开头添加 ASYNC
前缀。
ASYNC Post Load (0.0ms) (db time 2ms) SELECT "posts".* FROM "posts" LIMIT 100
源代码:显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation.rb, line 1138 def load_async with_connection do |c| return load if !c.async_enabled? unless loaded? result = exec_main_query(async: !c.current_transaction.joinable?) if result.is_a?(Array) @records = result else @future_result = result end @loaded = true end end self end
many?() 链接
如果存在多个记录,则返回 true。
源代码:显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation.rb, line 413 def many? return false if @none return super if block_given? return records.many? if loaded? limited_count > 1 end
new(attributes = nil, &block) 链接
从关系初始化新记录,同时保留当前范围。
期望参数与 ActiveRecord::Base.new 中的参数格式相同。
users = User.where(name: 'DHH')
user = users.new # => #<User id: nil, name: "DHH", created_at: nil, updated_at: nil>
您还可以将块传递给 new,并将新记录作为参数。
user = users.new { |user| user.name = 'Oscar' }
user.name # => Oscar
源代码:显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation.rb, line 125 def new(attributes = nil, &block) if attributes.is_a?(Array) attributes.collect { |attr| new(attr, &block) } else block = current_scope_restoring_block(&block) scoping { _new(attributes, &block) } end end
none?(*args) 链接
如果没有任何记录,则返回 true。
当给出模式参数时,此方法通过 case-equality 运算符 (===
) 检查 Enumerable
中的元素是否与模式匹配。
posts.none?(Comment) # => true or false
源代码:显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation.rb, line 378 def none?(*args) return true if @none return super if args.present? || block_given? empty? end
one?(*args) 链接
如果恰好有一条记录,则返回 true。
当给出模式参数时,此方法通过 case-equality 运算符 (===
) 检查 Enumerable
中的元素是否与模式匹配。
posts.one?(Post) # => true or false
源代码:显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation.rb, line 404 def one?(*args) return false if @none return super if args.present? || block_given? return records.one? if loaded? limited_count == 1 end
pretty_print(pp) 链接
源代码:显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation.rb, line 1264 def pretty_print(pp) subject = loaded? ? records : annotate("loading for pp") entries = subject.take([limit_value, 11].compact.min) entries[10] = "..." if entries.size == 11 pp.pp(entries) end
readonly?() 链接
源代码:显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation.rb, line 1278 def readonly? readonly_value end
reload() 链接
强制重新加载关系。
源代码:显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation.rb, line 1189 def reload reset load end
reset() 链接
源代码:显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation.rb, line 1194 def reset @future_result&.cancel @future_result = nil @delegate_to_model = false @to_sql = @arel = @loaded = @should_eager_load = nil @offsets = @take = nil @cache_keys = nil @cache_versions = nil @records = nil self end
scheduled?() 链接
如果关系已安排在后台线程池上,则返回 true
。
源代码:显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation.rb, line 1169 def scheduled? !!@future_result end
scope_for_create() 链接
源代码:显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation.rb, line 1231 def scope_for_create hash = where_clause.to_h(model.table_name, equality_only: true) create_with_value.each { |k, v| hash[k.to_s] = v } unless create_with_value.empty? hash end
scoping(all_queries: nil, &block) 链接
将所有查询范围限定到当前范围。
Comment.where(post_id: 1).scoping do
Comment.first
end
# SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = 1 ORDER BY "comments"."id" ASC LIMIT 1
如果传递了 all_queries: true
,则范围限定将应用于关系的所有查询,包括实例上的 update
和 delete
。一旦 all_queries
设置为 true,它就不能在嵌套块中设置为 false。
如果您想在执行块期间删除所有先前的范围(包括 default_scope),请检查 unscoped。
源代码:显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation.rb, line 541 def scoping(all_queries: nil, &block) registry = model.scope_registry if global_scope?(registry) && all_queries == false raise ArgumentError, "Scoping is set to apply to all queries and cannot be unset in a nested block." elsif already_in_scope?(registry) yield else _scoping(self, registry, all_queries, &block) end end
size() 链接
返回记录的大小。
源代码:显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation.rb, line 353 def size if loaded? records.length else count(:all) end end
to_ary() 链接
将关系对象转换为 Array
。
源代码:显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation.rb, line 337 def to_ary records.dup end
to_sql() 链接
返回关系的 sql 语句。
User.where(name: 'Oscar').to_sql
# SELECT "users".* FROM "users" WHERE "users"."name" = 'Oscar'
源代码:显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation.rb, line 1210 def to_sql @to_sql ||= if eager_loading? apply_join_dependency do |relation, join_dependency| relation = join_dependency.apply_column_aliases(relation) relation.to_sql end else model.with_connection do |conn| conn.unprepared_statement { conn.to_sql(arel) } end end end
touch_all(*names, time: nil) 链接
触碰当前关系中的所有记录,将 updated_at
/updated_on
属性设置为当前时间或指定的时间。它不会实例化所涉及的模型,也不会触发 Active Record 回调或验证。此方法可以传递属性名称和可选的时间参数。如果传递了属性名称,它们将与 updated_at
/updated_on
属性一起更新。如果没有传递时间参数,则当前时间将用作默认值。
示例
# Touch all records
Person.all.touch_all
# => "UPDATE \"people\" SET \"updated_at\" = '2018-01-04 22:55:23.132670'"
# Touch multiple records with a custom attribute
Person.all.touch_all(:created_at)
# => "UPDATE \"people\" SET \"updated_at\" = '2018-01-04 22:55:23.132670', \"created_at\" = '2018-01-04 22:55:23.132670'"
# Touch multiple records with a specified time
Person.all.touch_all(time: Time.new(2020, 5, 16, 0, 0, 0))
# => "UPDATE \"people\" SET \"updated_at\" = '2020-05-16 00:00:00'"
# Touch records with scope
Person.where(name: 'David').touch_all
# => "UPDATE \"people\" SET \"updated_at\" = '2018-01-04 22:55:23.132670' WHERE \"people\".\"name\" = 'David'"
源代码:显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation.rb, line 969 def touch_all(*names, time: nil) update_all model.touch_attributes_with_time(*names, time: time) end
update_all(updates) 链接
使用给定的详细信息更新当前关系中的所有记录。此方法构造单个 SQL UPDATE 语句并将其直接发送到数据库。它不会实例化所涉及的模型,也不会触发 Active Record 回调或验证。但是,传递给 update_all
的值仍然会通过 Active Record 的正常类型转换和序列化。返回受影响的行数。
注意:由于 Active Record 回调不会触发,因此此方法不会自动更新 updated_at
/updated_on
列。
参数
-
updates
- 表示 SQL 语句的 SET 部分的字符串、数组或哈希。任何提供的字符串都将进行类型转换,除非您使用Arel.sql
。(不要将用户提供的值传递给Arel.sql
。)
示例
# Update all customers with the given attributes
Customer.update_all wants_email: true
# Update all books with 'Rails' in their title
Book.where('title LIKE ?', '%Rails%').update_all(author: 'David')
# Update all books that match conditions, but limit it to 5 ordered by date
Book.where('title LIKE ?', '%Rails%').order(:created_at).limit(5).update_all(author: 'David')
# Update all invoices and set the number column to its id value.
Invoice.update_all('number = id')
# Update all books with 'Rails' in their title
Book.where('title LIKE ?', '%Rails%').update_all(title: Arel.sql("title + ' - volume 1'"))
源代码:显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation.rb, line 588 def update_all(updates) raise ArgumentError, "Empty list of attributes to change" if updates.blank? return 0 if @none if updates.is_a?(Hash) if model.locking_enabled? && !updates.key?(model.locking_column) && !updates.key?(model.locking_column.to_sym) attr = table[model.locking_column] updates[attr.name] = _increment_attribute(attr) end values = _substitute_values(updates) else values = Arel.sql(model.sanitize_sql_for_assignment(updates, table.name)) end model.with_connection do |c| arel = eager_loading? ? apply_join_dependency.arel : build_arel(c) arel.source.left = table group_values_arel_columns = arel_columns(group_values.uniq) having_clause_ast = having_clause.ast unless having_clause.empty? key = if model.composite_primary_key? primary_key.map { |pk| table[pk] } else table[primary_key] end stmt = arel.compile_update(values, key, having_clause_ast, group_values_arel_columns) c.update(stmt, "#{model} Update All").tap { reset } end end
update_counters(counters) 链接
更新当前关系中记录的计数器。
参数
-
counter
- 包含要更新的字段名称作为键和要更新的数量作为值的Hash
。 -
:touch
选项 - 更新时触碰时间戳列。 -
如果传递了属性名称,它们将与 update_at/on 属性一起更新。
示例
# For Posts by a given author increment the comment_count by 1.
Post.where(author_id: author.id).update_counters(comment_count: 1)
源代码:显示 | 在 GitHub 上
# File activerecord/lib/active_record/relation.rb, line 926 def update_counters(counters) touch = counters.delete(:touch) updates = {} counters.each do |counter_name, value| attr = table[counter_name] updates[attr.name] = _increment_attribute(attr, value) end if touch names = touch if touch != true names = Array.wrap(names) options = names.extract_options! touch_updates = model.touch_attributes_with_time(*names, **options) updates.merge!(touch_updates) unless touch_updates.empty? end update_all updates end
upsert(attributes, **kwargs) 链接
使用单个 SQL INSERT 语句将单条记录更新或插入(upsert)到数据库中。它不会实例化任何模型,也不会触发 Active Record 回调或验证。不过,传递的值会经过 Active Record 的类型转换和序列化。
有关文档,请参见 upsert_all
。
源代码:显示 | 在 GitHub 上查看
# File activerecord/lib/active_record/relation.rb, line 800 def upsert(attributes, **kwargs) upsert_all([ attributes ], **kwargs) end
upsert_all(attributes, on_duplicate: :update, update_only: nil, returning: nil, unique_by: nil, record_timestamps: nil) 链接
使用单个 SQL INSERT 语句将多条记录更新或插入(upsert)到数据库中。它不会实例化任何模型,也不会触发 Active Record 回调或验证。不过,传递的值会经过 Active Record 的类型转换和序列化。
attributes
参数是一个 Array
的 Hashes。每个 Hash
决定了单行的属性,并且必须具有相同的键。
返回一个 ActiveRecord::Result
,其内容基于 :returning
(见下文)。
默认情况下,upsert_all
会更新所有可更新的列,这些列包含除主键、只读列以及可选的 unique_by
覆盖的列以外的所有列。
选项
- :returning
-
(仅限 PostgreSQL、SQLite3 和 MariaDB) 要为所有成功插入的记录返回的属性数组,默认情况下是主键。传递
returning: %w[ id name ]
来获取 id 和 name,或returning: false
来完全省略底层的RETURNING
SQL 子句。如果您需要对返回值进行更多控制,也可以传递一个 SQL 字符串(例如,
returning: Arel.sql("id, name as new_name")
)。 - :unique_by
-
(仅限 PostgreSQL 和 SQLite) 默认情况下,行被认为是通过表上的每个唯一索引来区分的。任何重复的行都会被跳过。
要根据单个唯一索引跳过行,请传递
:unique_by
。假设有一个 Book 模型,其中不应出现重复的 ISBN,但如果任何行具有现有 ID 或未被其他唯一索引区分,则会引发
ActiveRecord::RecordNotUnique
错误。唯一索引可以通过列或名称来标识
unique_by: :isbn unique_by: %i[ author_id name ] unique_by: :index_books_on_isbn
由于 :unique_by
依赖于数据库中的索引信息,因此建议将其与 Active Record 的 schema_cache 配合使用。
- :on_duplicate
-
配置发生冲突时将使用的 SQL 更新语句。
注意:如果使用此选项,则必须自己提供要更新的所有列。
示例
Commodity.upsert_all( [ { id: 2, name: "Copper", price: 4.84 }, { id: 4, name: "Gold", price: 1380.87 }, { id: 6, name: "Aluminium", price: 0.35 } ], on_duplicate: Arel.sql("price = GREATEST(commodities.price, EXCLUDED.price)") )
请参阅相关的
:update_only
选项。这两个选项不能同时使用。 - :update_only
-
提供一个列名列表,这些列将在发生冲突时更新。如果未提供,
upsert_all
会更新所有可更新的列,这些列包含除主键、只读列以及可选的unique_by
覆盖的列以外的所有列。示例
Commodity.upsert_all( [ { id: 2, name: "Copper", price: 4.84 }, { id: 4, name: "Gold", price: 1380.87 }, { id: 6, name: "Aluminium", price: 0.35 } ], update_only: [:price] # Only prices will be updated )
请参阅相关的
:on_duplicate
选项。这两个选项不能同时使用。 - :record_timestamps
-
默认情况下,时间戳列的自动设置由模型的
record_timestamps
配置控制,与典型行为一致。要覆盖此行为并强制以某种方式自动设置时间戳列,请传递
:record_timestamps
。record_timestamps: true # Always set timestamps automatically record_timestamps: false # Never set timestamps automatically
示例
# Inserts multiple records, performing an upsert when records have duplicate ISBNs.
# Here "Eloquent Ruby" overwrites "Rework" because its ISBN is duplicate.
Book.upsert_all([
{ title: "Rework", author: "David", isbn: "1" },
{ title: "Eloquent Ruby", author: "Russ", isbn: "1" }
], unique_by: :isbn)
Book.find_by(isbn: "1").title # => "Eloquent Ruby"
源代码:显示 | 在 GitHub 上查看
# File activerecord/lib/active_record/relation.rb, line 910 def upsert_all(attributes, on_duplicate: :update, update_only: nil, returning: nil, unique_by: nil, record_timestamps: nil) InsertAll.execute(self, attributes, on_duplicate: on_duplicate, update_only: update_only, returning: returning, unique_by: unique_by, record_timestamps: record_timestamps) end
values() 链接
源代码:显示 | 在 GitHub 上查看
# File activerecord/lib/active_record/relation.rb, line 1282 def values @values.dup end
实例受保护方法
load_records(records) 链接
源代码:显示 | 在 GitHub 上查看
# File activerecord/lib/active_record/relation.rb, line 1331 def load_records(records) @records = records.freeze @loaded = true end