- #
- 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_UPDATE_AND_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 |
Attributes
| [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: {}) Link
# 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) Link
比较两个 relation 的相等性。
any?(*args) Link
如果存在任何记录,则返回 true。
当提供模式参数时,此方法通过 case-equality 运算符 (===) 检查 Enumerable 中的元素是否与模式匹配。
posts.any?(Post) # => true or false
blank?() Link
如果 relation 为空,则返回 true。
cache_key(timestamp_column = "updated_at") Link
返回一个稳定的缓存键,可用于标识此查询。缓存键是通过 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)
cache_key_with_version() Link
返回一个缓存键以及版本。
cache_version(timestamp_column = :updated_at) Link
返回一个缓存版本,可与缓存键一起形成可回收的缓存方案。缓存版本是通过匹配查询的记录数以及最后更新记录的时间戳构建的。当匹配查询的新记录出现,或任何现有记录被更新或删除时,缓存版本会发生变化。
如果集合已加载,该方法将遍历记录以生成时间戳,否则它将触发一个 SQL 查询,例如
SELECT COUNT(*), MAX("products"."updated_at") FROM "products" WHERE (name like '%Cosmic Encounter%')
create(attributes = nil, &block) Link
尝试使用 relation 中定义的相同范围属性创建新记录。如果验证失败,则返回初始化后的对象。
参数格式与 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, ...>
create!(attributes = nil, &block) Link
类似于 create,但调用基类上的 create!。如果发生验证错误,将引发异常。
参数格式与 ActiveRecord::Base.create! 相同。
# 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) Link
尝试在具有单个或多个列唯一数据库约束的表中创建具有给定属性的记录。如果已存在具有这些唯一约束的行,则会捕获通常会引发此插入的异常,并使用 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 在这种情况下返回的内容。
# File activerecord/lib/active_record/relation.rb, line 273 def create_or_find_by(attributes, &block) with_connection do |connection| record = nil transaction(requires_new: true) do record = create(attributes, &block) record._last_transaction_return_status || raise(ActiveRecord::Rollback) end record 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) Link
类似于 create_or_find_by,但调用 create!,因此如果创建的记录无效,将引发异常。
# File activerecord/lib/active_record/relation.rb, line 293 def create_or_find_by!(attributes, &block) with_connection do |connection| record = nil transaction(requires_new: true) do record = create!(attributes, &block) record._last_transaction_return_status || raise(ActiveRecord::Rollback) end record rescue ActiveRecord::RecordNotUnique if connection.transaction_open? where(attributes).lock.find_by!(attributes) else find_by!(attributes) end end end
delete(id_or_array) Link
delete_all() Link
在不先实例化记录的情况下删除记录,因此不会调用 #destroy 方法,也不会调用回调。这是一个直接发送到数据库的单一 SQL DELETE 语句,比 destroy_all 高效得多。请注意 relations,特别是关联上定义的 :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
# File activerecord/lib/active_record/relation.rb, line 1031 def delete_all return 0 if @none invalid_methods = INVALID_METHODS_FOR_UPDATE_AND_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 : arel() arel.source.left = table key = if model.composite_primary_key? primary_key.map { |pk| table[pk] } else table[primary_key] end stmt = arel.compile_delete(key) c.delete(stmt, "#{model} Delete All").tap { reset } end end
delete_by(*args) Link
查找并删除所有匹配指定条件的记录。这是 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)
destroy(id) Link
destroy_all() Link
通过实例化每个记录并调用其 #destroy 方法来销毁记录。每个对象的(回调)都会被执行(包括 :dependent 关联选项)。返回被销毁的对象集合;每个对象都将被冻结,以反映不应进行任何更改(因为它们无法持久化)。
注意:当一次删除大量记录时,实例化、(回调)执行和删除每个记录可能非常耗时。它会为每条记录生成至少一个 SQL DELETE 查询(或者可能更多,以强制执行您的(回调))。如果您想快速删除大量行,而不必担心它们的关联或(回调),请改用 delete_all。
示例¶ ↑
Person.where(age: 0..18).destroy_all
destroy_by(*args) Link
查找并销毁所有匹配指定条件的记录。这是 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)
eager_loading?() Link
如果 relation 需要贪婪加载,则返回 true。
empty?() Link
如果没有记录,则返回 true。
explain(*options) Link
在由此 relation 触发的查询或查询上运行 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 查询接口指南 中查看更多详细信息。
find_or_create_by(attributes, &block) Link
查找具有给定属性的第一个记录,如果找不到则创建具有该属性的记录。
# 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。因此,如果表没有相关的唯一约束,您最终可能会得到两个或多个相似的记录。
find_or_create_by!(attributes, &block) Link
类似于 find_or_create_by,但调用 create!,因此如果创建的记录无效,将引发异常。
find_or_initialize_by(attributes, &block) Link
类似于 find_or_create_by,但调用 new 而不是 create。
initialize_copy(other) Link
insert(attributes, returning: nil, unique_by: nil, record_timestamps: nil) Link
使用单一 SQL INSERT 语句将单条记录插入数据库。它不会实例化任何模型,也不会触发 Active Record 回调或验证。尽管传入的值会经过 Active Record 的类型转换和序列化。
有关文档,请参阅 insert_all。
insert!(attributes, returning: nil, record_timestamps: nil) Link
使用单一 SQL INSERT 语句将单条记录插入数据库。它不会实例化任何模型,也不会触发 Active Record 回调或验证。尽管传入的值会经过 Active Record 的类型转换和序列化。
有关更多信息,请参阅 insert_all!。
insert_all(attributes, returning: nil, unique_by: nil, record_timestamps: nil) Link
使用单一 SQL INSERT 语句将多条记录插入数据库。它不会实例化任何模型,也不会触发 Active Record 回调或验证。尽管传入的值会经过 Active Record 的类型转换和序列化。
attributes 参数是一个 Hashes 的 Array。每个 Hash 决定单行的属性,并且必须具有相同的键。
默认情况下,行被认为是唯一的,因为表上存在每个唯一索引。任何重复的行都会被跳过。使用 :unique_by(见下文)进行覆盖。
返回一个 ActiveRecord::Result,其内容基于 :returning(见下文)。
选项¶ ↑
- :returning
-
(仅限 PostgreSQL、SQLite3 和 MariaDB) 一个属性数组,用于返回所有成功插入的记录,默认为主键。传递
returning: %w[ id name ]同时返回 id 和 name,或returning: false完全省略底层的RETURNINGSQL 子句。您还可以传递一个 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_timestampsrecord_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" } ])
# File activerecord/lib/active_record/relation.rb, line 743 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) Link
使用单一 SQL INSERT 语句将多条记录插入数据库。它不会实例化任何模型,也不会触发 Active Record 回调或验证。尽管传入的值会经过 Active Record 的类型转换和序列化。
attributes 参数是一个 Hashes 的 Array。每个 Hash 决定单行的属性,并且必须具有相同的键。
如果任何行违反表上的唯一索引,将引发 ActiveRecord::RecordNotUnique。在这种情况下,不会插入任何行。
要跳过重复行,请参阅 insert_all。要替换它们,请参阅 upsert_all。
返回一个 ActiveRecord::Result,其内容基于 :returning(见下文)。
选项¶ ↑
- :returning
-
(仅限 PostgreSQL、SQLite3 和 MariaDB) 一个属性数组,用于返回所有成功插入的记录,默认为主键。传递
returning: %w[ id name ]同时返回 id 和 name,或returning: false完全省略底层的RETURNINGSQL 子句。您还可以传递一个 SQL 字符串,如果您需要对返回值进行更多控制(例如,
returning: Arel.sql("id, name as new_name"))。 - :record_timestamps
-
默认情况下,时间戳列的自动设置由模型的
record_timestamps配置控制,与典型的行为一致。要覆盖此设置并强制自动设置时间戳列(无论哪种方式),请传递
:record_timestampsrecord_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" } ])
inspect() Link
# File activerecord/lib/active_record/relation.rb, line 1308 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() Link
既是 JOIN 又是预加载的关联。在这种情况下,我们应该只贪婪加载它们。请注意,这是一个简单的实现,因为字符串和符号可能表示相同的关联,但无法在此处匹配。此外,我们可能有关联的哈希,它们部分匹配,例如 { a: :b } & { a: [:b, :c] }
load(&block) Link
如果记录尚未加载,则从数据库加载它们。如果出于某种原因需要在使用记录之前显式加载它们,则可以使用此方法。返回值是 relation 本身,而不是记录。
Post.where(published: true).load # => #<ActiveRecord::Relation>
load_async() Link
将查询安排从后台线程池执行。
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
# File activerecord/lib/active_record/relation.rb, line 1156 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?() Link
如果存在多于一条记录,则返回 true。
new(attributes = nil, &block) Link
从 relation 初始化新记录,同时保留当前范围。
参数格式与 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
none?(*args) Link
如果没有记录,则返回 true。
当提供模式参数时,此方法通过 case-equality 运算符 (===) 检查 Enumerable 中的元素是否与模式匹配。
posts.none?(Comment) # => true or false
one?(*args) Link
如果恰好有一条记录,则返回 true。
当提供模式参数时,此方法通过 case-equality 运算符 (===) 检查 Enumerable 中的元素是否与模式匹配。
posts.one?(Post) # => true or false
pretty_print(pp) Link
readonly?() Link
reload() Link
强制重新加载 relation。
reset() Link
# File activerecord/lib/active_record/relation.rb, line 1212 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?() Link
如果 relation 已在后台线程池中安排,则返回 true。
scope_for_create() Link
scoping(all_queries: nil, &block) Link
将所有查询范围限定为当前范围。
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,范围将应用于 relation 的所有查询,包括实例上的 update 和 delete。一旦 all_queries 设置为 true,在嵌套块中就不能将其设置为 false。
如果您想在块执行期间删除所有之前的范围(包括 default_scope),请检查 unscoped。
# File activerecord/lib/active_record/relation.rb, line 551 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() Link
返回记录的大小。
to_sql() Link
返回 relation 的 SQL 语句。
User.where(name: 'Oscar').to_sql # SELECT "users".* FROM "users" WHERE "users"."name" = 'Oscar'
# File activerecord/lib/active_record/relation.rb, line 1228 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) Link
触碰当前 relation 中的所有记录,将 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'"
update_all(updates) Link
使用提供的详细信息更新当前 relation 中的所有记录。此方法构建一个单一的 SQL UPDATE 语句并直接发送到数据库。它不会实例化涉及的模型,也不会触发 Active Record 回调或验证。但是,传递给 update_all 的值仍会经过 Active Record 的正常类型转换和序列化。返回受影响的行数。
注意:由于不会触发 Active Record 回调,此方法不会自动更新 updated_at/updated_on 列。
参数¶ ↑
示例¶ ↑
# 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'"))
# File activerecord/lib/active_record/relation.rb, line 598 def update_all(updates) raise ArgumentError, "Empty list of attributes to change" if updates.blank? return 0 if @none invalid_methods = INVALID_METHODS_FOR_UPDATE_AND_DELETE_ALL.select do |method| value = @values[method] method == :distinct ? value : value&.any? end if invalid_methods.any? ActiveRecord.deprecator.warn <<~MESSAGE `#{invalid_methods.join(', ')}` is not supported by `update_all` and was never included in the generated query. Calling `#{invalid_methods.join(', ')}` with `update_all` will raise an error in Rails 8.2. MESSAGE end 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 : arel() arel.source.left = table key = if model.composite_primary_key? primary_key.map { |pk| table[pk] } else table[primary_key] end stmt = arel.compile_update(values, key) c.update(stmt, "#{model} Update All").tap { reset } end end
update_counters(counters) Link
更新当前 relation 中记录的计数器。
参数¶ ↑
-
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)
# File activerecord/lib/active_record/relation.rb, line 946 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) Link
使用单一 SQL INSERT 语句将单条记录更新或插入(upsert)到数据库。它不会实例化任何模型,也不会触发 Active Record 回调或验证。尽管传入的值会经过 Active Record 的类型转换和序列化。
有关文档,请参阅 upsert_all。
upsert_all(attributes, on_duplicate: :update, update_only: nil, returning: nil, unique_by: nil, record_timestamps: nil) Link
使用单一 SQL INSERT 语句将多条记录更新或插入(upsert)到数据库。它不会实例化任何模型,也不会触发 Active Record 回调或验证。尽管传入的值会经过 Active Record 的类型转换和序列化。
attributes 参数是一个 Hashes 的 Array。每个 Hash 决定单行的属性,并且必须具有相同的键。
返回一个 ActiveRecord::Result,其内容基于 :returning(见下文)。
默认情况下,当发生冲突时,upsert_all 将更新所有可更新的列。这些是除主键、只读列和 unique_by 选项覆盖的列之外的所有列。
选项¶ ↑
- :returning
-
(仅限 PostgreSQL、SQLite3 和 MariaDB) 一个属性数组,用于返回所有成功 upsert 的记录,默认为主键。传递
returning: %w[ id name ]同时返回 id 和 name,或returning: false完全省略底层的RETURNINGSQL 子句。您还可以传递一个 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 UPDATE 语句。
注意:如果您使用此选项,则必须自己提供所有要更新的列。
示例
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_timestampsrecord_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"
# File activerecord/lib/active_record/relation.rb, line 930 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