跳至内容 跳至搜索

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]
 
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(klass, table: klass.arel_table, predicate_builder: klass.predicate_builder, values: {})

# File activerecord/lib/active_record/relation.rb, line 28
def initialize(klass, table: klass.arel_table, predicate_builder: klass.predicate_builder, values: {})
  @klass  = klass
  @table  = table
  @values = values
  @loaded = false
  @predicate_builder = predicate_builder
  @delegate_to_klass = false
  @future_result = nil
  @records = nil
  @async = false
  @none = false
end

实例公共方法

==(other)

比较两个关系是否相等。

# File activerecord/lib/active_record/relation.rb, line 813
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。

当给定模式参数时,此方法会通过大小写相等运算符 (===) 检查 Enumerable 中的元素是否与该模式匹配。

posts.any?(Post) # => true or false
# File activerecord/lib/active_record/relation.rb, line 312
def any?(*args)
  return false if @none

  return super if args.present? || block_given?
  !empty?
end

blank?()

如果关系为空,则返回 true。

# File activerecord/lib/active_record/relation.rb, line 834
def blank?
  records.blank?
end

build(attributes = nil, &block)

别名:new

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)
# File activerecord/lib/active_record/relation.rb, line 359
def cache_key(timestamp_column = "updated_at")
  @cache_keys ||= {}
  @cache_keys[timestamp_column] ||= klass.collection_cache_key(self, timestamp_column)
end

cache_key_with_version()

返回一个缓存键以及版本。

# File activerecord/lib/active_record/relation.rb, line 438
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%')
# File activerecord/lib/active_record/relation.rb, line 386
def cache_version(timestamp_column = :updated_at)
  if 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, ...>
# File activerecord/lib/active_record/relation.rb, line 98
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! 相同的格式提供参数。

# File activerecord/lib/active_record/relation.rb, line 113
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,不会出现此问题)。

如果所有给定属性都受唯一约束覆盖,则此方法将返回一条记录(除非触发 INSERT -> DELETE -> SELECT 竞争条件),但如果尝试创建并由于验证错误而失败,则不会持久保存,您将获得 create 在这种情况下返回的内容。

# File activerecord/lib/active_record/relation.rb, line 215
def create_or_find_by(attributes, &block)
  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

create_or_find_by!(attributes, &block)

类似于 create_or_find_by,但调用 create!,因此如果创建的记录无效,则会引发异常。

# File activerecord/lib/active_record/relation.rb, line 228
def create_or_find_by!(attributes, &block)
  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

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
# File activerecord/lib/active_record/relation.rb, line 646
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

  arel = eager_loading? ? apply_join_dependency.arel : build_arel
  arel.source.left = table

  group_values_arel_columns = arel_columns(group_values.uniq)
  having_clause_ast = having_clause.ast unless having_clause.empty?
  stmt = arel.compile_delete(table[primary_key], having_clause_ast, group_values_arel_columns)

  klass.connection.delete(stmt, "#{klass} Delete All").tap { reset }
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)
# File activerecord/lib/active_record/relation.rb, line 689
def delete_by(*args)
  where(*args).delete_all
end

destroy_all()

通过实例化每条记录并调用其 #destroy 方法来销毁记录。执行每个对象的回调(包括 :dependent 关联选项)。返回被销毁的对象集合;每个对象都将被冻结,以反映不应该进行任何更改(因为它们无法持久化)。

注意:当一次移除多条记录时,实例化、回调执行和每条记录的删除可能非常耗时。它会为每条记录生成至少一个 SQL DELETE 查询(或可能更多,以强制执行回调)。如果你想快速删除多行,而不关心它们的关联或回调,请改用 delete_all

示例

Person.where(age: 0..18).destroy_all
# File activerecord/lib/active_record/relation.rb, line 624
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)
# File activerecord/lib/active_record/relation.rb, line 676
def destroy_by(*args)
  where(*args).destroy_all
end

eager_loading?()

如果关联需要急切加载,则返回 true。

# File activerecord/lib/active_record/relation.rb, line 798
def eager_loading?
  @should_eager_load ||=
    eager_load_values.any? ||
    includes_values.any? && (joined_includes_values.any? || references_eager_loaded_tables?)
end

empty?()

如果没有记录,则返回 true。

# File activerecord/lib/active_record/relation.rb, line 283
def empty?
  return true if @none

  if loaded?
    records.empty?
  else
    !exists?
  end
end

encode_with(coder)

序列化关联对象 Array

# File activerecord/lib/active_record/relation.rb, line 269
def encode_with(coder)
  coder.represent_seq(nil, records)
end

explain(*options)

在此关系触发查询或查询上运行 EXPLAIN,并将结果作为字符串返回。字符串的格式模仿数据库 shell 打印的字符串。

请注意,此方法实际上会运行查询,因为在进行急切加载时,一些查询的结果会被后续查询需要。

请参阅Active Record Query Interface 指南中的更多详细信息。

# File activerecord/lib/active_record/relation.rb, line 253
def explain(*options)
  exec_explain(collecting_queries_for_explain { exec_queries }, 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。因此,如果表没有相关的唯一性约束,则最终可能会得到两条或更多类似的记录。

# File activerecord/lib/active_record/relation.rb, line 175
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!,因此如果创建的记录无效,则会引发异常。

# File activerecord/lib/active_record/relation.rb, line 182
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

# File activerecord/lib/active_record/relation.rb, line 240
def find_or_initialize_by(attributes, &block)
  find_by(attributes) || new(attributes, &block)
end

initialize_copy(other)

# File activerecord/lib/active_record/relation.rb, line 41
def initialize_copy(other)
  @values = @values.dup
  reset
end

inspect()

# File activerecord/lib/active_record/relation.rb, line 846
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] }

# File activerecord/lib/active_record/relation.rb, line 808
def joined_includes_values
  includes_values & joins_values
end

load(&block)

如果尚未加载记录,则导致从数据库加载记录。如果出于某种原因需要在实际使用之前显式加载某些记录,则可以使用此方法。返回值是关系本身,而不是记录。

Post.where(published: true).load # => #<ActiveRecord::Relation>
# File activerecord/lib/active_record/relation.rb, line 740
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 才能实际并发执行查询。否则,它默认为在前台执行查询。

当启用事务性固定装置时,load_async 还会在测试环境中回退到在前台执行。

如果查询实际上是在后台执行的,则 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 711
def load_async
  return load if !connection.async_enabled?

  unless loaded?
    result = exec_main_query(async: connection.current_transaction.closed?)

    if result.is_a?(Array)
      @records = result
    else
      @future_result = result
    end
    @loaded = true
  end

  self
end

many?()

如果有多个记录,则返回 true。

# File activerecord/lib/active_record/relation.rb, line 334
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
别名也为:build
# File activerecord/lib/active_record/relation.rb, line 69
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。

当给定模式参数时,此方法会通过大小写相等运算符 (===) 检查 Enumerable 中的元素是否与该模式匹配。

posts.none?(Comment) # => true or false
# File activerecord/lib/active_record/relation.rb, line 299
def none?(*args)
  return true if @none

  return super if args.present? || block_given?
  empty?
end

one?(*args)

如果只有一条记录,则返回 true。

当给定模式参数时,此方法会通过大小写相等运算符 (===) 检查 Enumerable 中的元素是否与该模式匹配。

posts.one?(Post) # => true or false
# File activerecord/lib/active_record/relation.rb, line 325
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)

# File activerecord/lib/active_record/relation.rb, line 824
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

reload()

强制重新加载关系。

# File activerecord/lib/active_record/relation.rb, line 750
def reload
  reset
  load
end

reset()

# File activerecord/lib/active_record/relation.rb, line 755
def reset
  @future_result&.cancel
  @future_result = nil
  @delegate_to_klass = false
  @to_sql = @arel = @loaded = @should_eager_load = nil
  @offsets = @take = nil
  @cache_keys = nil
  @cache_versions = nil
  @records = nil
  self
end

scheduled?()

如果关系已在后台线程池中计划,则返回 true

# File activerecord/lib/active_record/relation.rb, line 730
def scheduled?
  !!@future_result
end

scope_for_create()

# File activerecord/lib/active_record/relation.rb, line 791
def scope_for_create
  hash = where_clause.to_h(klass.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,则范围限定将适用于关系的所有查询,包括实例上的 updatedelete。一旦 all_queries 设置为 true,它就不能在嵌套块中设置为 false。

如果你想在执行块期间删除所有先前的范围(包括 default_scope),请检查 unscoped。

# File activerecord/lib/active_record/relation.rb, line 460
def scoping(all_queries: nil, &block)
  registry = klass.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()

返回记录的大小。

# File activerecord/lib/active_record/relation.rb, line 274
def size
  if loaded?
    records.length
  else
    count(:all)
  end
end

to_a()

别名:to_ary

to_ary()

将关系对象转换为 Array

别名:to_a
# File activerecord/lib/active_record/relation.rb, line 258
def to_ary
  records.dup
end

to_sql()

返回关系的 SQL 语句。

User.where(name: 'Oscar').to_sql
# SELECT "users".* FROM "users"  WHERE "users"."name" = 'Oscar'
# File activerecord/lib/active_record/relation.rb, line 771
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
    conn = klass.connection
    conn.unprepared_statement { conn.to_sql(arel) }
  end
end

touch_all(*names, time: nil)

触及当前关系中的所有记录,将 updated_at/updated_on 属性设置为当前时间或指定的时间。它不会实例化涉及的模型,也不会触发 ActiveRecord 回调或验证。此方法可以传递属性名称和可选的时间参数。如果传递属性名称,它们将与 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'"
# File activerecord/lib/active_record/relation.rb, line 604
def touch_all(*names, time: nil)
  update_all klass.touch_attributes_with_time(*names, time: time)
end

update_all(updates)

使用给定的详细信息更新当前关系中的所有记录。此方法构建一个单独的 SQL UPDATE 语句并直接将其发送到数据库。它不会实例化涉及的模型,也不会触发 ActiveRecord 回调或验证。但是,传递给 update_all 的值仍将通过 ActiveRecord 的正常类型转换和序列化。返回受影响的行数。

注意:由于未触发 ActiveRecord 回调,此方法不会自动更新 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'"))
# File activerecord/lib/active_record/relation.rb, line 507
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 klass.locking_enabled? &&
        !updates.key?(klass.locking_column) &&
        !updates.key?(klass.locking_column.to_sym)
      attr = table[klass.locking_column]
      updates[attr.name] = _increment_attribute(attr)
    end
    values = _substitute_values(updates)
  else
    values = Arel.sql(klass.sanitize_sql_for_assignment(updates, table.name))
  end

  arel = eager_loading? ? apply_join_dependency.arel : build_arel
  arel.source.left = table

  group_values_arel_columns = arel_columns(group_values.uniq)
  having_clause_ast = having_clause.ast unless having_clause.empty?
  stmt = arel.compile_update(values, table[primary_key], having_clause_ast, group_values_arel_columns)
  klass.connection.update(stmt, "#{klass} Update All").tap { reset }
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)
# File activerecord/lib/active_record/relation.rb, line 561
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 = klass.touch_attributes_with_time(*names, **options)
    updates.merge!(touch_updates) unless touch_updates.empty?
  end

  update_all updates
end

values()

# File activerecord/lib/active_record/relation.rb, line 838
def values
  @values.dup
end

实例受保护方法

load_records(records)

# File activerecord/lib/active_record/relation.rb, line 887
def load_records(records)
  @records = records.freeze
  @loaded = true
end