跳至内容 跳至搜索

Active Record 持久性

命名空间
方法
B
D
I
N
P
R
S
T
U

实例公共方法

becomes(klass)

返回指定 klass 的实例,其中包含当前记录的属性。这在单表继承 (STI) 结构中非常有用,其中您希望子类显示为超类。这可以与 Action Pack 中的记录标识一起使用,例如,允许 Client < Company 执行类似于渲染 partial: @client.becomes(Company) 的操作,以使用 companies/company 部分而不是 clients/client 渲染该实例。

注意:新实例将与原始类共享对相同属性的链接。因此,STI 列值仍将相同。对任一实例上的属性的任何更改都将影响这两个实例。这包括新实例完成的任何属性初始化。

如果您还想更改 STI 列,请使用 becomes!

# File activerecord/lib/active_record/persistence.rb, line 814
def becomes(klass)
  became = klass.allocate

  became.send(:initialize) do |becoming|
    @attributes.reverse_merge!(becoming.instance_variable_get(:@attributes))
    becoming.instance_variable_set(:@attributes, @attributes)
    becoming.instance_variable_set(:@mutations_from_database, @mutations_from_database ||= nil)
    becoming.instance_variable_set(:@new_record, new_record?)
    becoming.instance_variable_set(:@destroyed, destroyed?)
    becoming.errors.copy!(errors)
  end

  became
end

becomes!(klass)

becomes 的包装器,它还更改实例的 STI 列值。如果您想在数据库中保留更改的类,这将特别有用。

注意:旧实例的 STI 列值也将更改,因为这两个对象共享同一组属性。

# File activerecord/lib/active_record/persistence.rb, line 835
def becomes!(klass)
  became = becomes(klass)
  sti_type = nil
  if !klass.descends_from_active_record?
    sti_type = klass.sti_name
  end
  became.public_send("#{klass.inheritance_column}=", sti_type)
  became
end

decrement(attribute, by = 1)

如果 attributenil,则将其初始化为零,并减去作为 by 传递的值(默认为 1)。减法直接在底层属性上执行,不会调用 setter。仅对基于数字的属性有意义。返回 self

# File activerecord/lib/active_record/persistence.rb, line 982
def decrement(attribute, by = 1)
  increment(attribute, -by)
end

decrement!(attribute, by = 1, touch: nil)

decrement 的包装器,它将更新写入数据库。仅更新 attribute;记录本身不会保存。这意味着任何其他修改的属性仍将是脏的。验证和回调被跳过。支持 update_counters 中的 touch 选项,有关更多信息,请参阅该选项。返回 self

# File activerecord/lib/active_record/persistence.rb, line 992
def decrement!(attribute, by = 1, touch: nil)
  increment!(attribute, -by, touch: touch)
end

delete()

删除数据库中的记录,并冻结此实例以反映不应进行任何更改(因为它们无法持久化)。返回冻结的实例。

该行只是使用 SQL DELETE 语句在记录的主键上删除,并且不执行任何回调。

请注意,这也将删除标记为 #readonly? 的记录。

要强制执行对象的 before_destroyafter_destroy 回调或任何 :dependent 关联选项,请使用 destroy

# File activerecord/lib/active_record/persistence.rb, line 766
def delete
  _delete_row if persisted?
  @destroyed = true
  @previously_new_record = false
  freeze
end

destroy()

删除数据库中的记录,并冻结此实例以反映不应进行任何更改(因为它们无法持久化)。

有一系列与 destroy 关联的回调。如果 before_destroy 回调抛出 :abort,则取消操作,destroy 返回 false。有关更多详细信息,请参见 ActiveRecord::Callbacks

# File activerecord/lib/active_record/persistence.rb, line 780
def destroy
  _raise_readonly_record_error if readonly?
  destroy_associations
  @_trigger_destroy_callback ||= persisted? && destroy_row > 0
  @destroyed = true
  @previously_new_record = false
  freeze
end

destroy!()

删除数据库中的记录,并冻结此实例以反映不应进行任何更改(因为它们无法持久化)。

有一系列与 destroy! 关联的回调。如果 before_destroy 回调抛出 :abort,则取消操作,destroy! 引发 ActiveRecord::RecordNotDestroyed。有关更多详细信息,请参见 ActiveRecord::Callbacks

# File activerecord/lib/active_record/persistence.rb, line 796
def destroy!
  destroy || _raise_record_not_destroyed
end

destroyed?()

如果此对象已销毁,则返回 true,否则返回 false。

# File activerecord/lib/active_record/persistence.rb, line 682
def destroyed?
  @destroyed
end

increment(attribute, by = 1)

如果 attributenil,则将其初始化为零,并添加作为 by 传递的值(默认为 1)。增量直接在底层属性上执行,不会调用任何设置器。仅对基于数字的属性有意义。返回 self

# File activerecord/lib/active_record/persistence.rb, line 959
def increment(attribute, by = 1)
  self[attribute] ||= 0
  self[attribute] += by
  self
end

increment!(attribute, by = 1, touch: nil)

包装器围绕 increment 将更新写入数据库。仅更新 attribute;记录本身不会被保存。这意味着任何其他修改的属性仍将是脏的。Validations 和回调被跳过。支持 update_counters 中的 touch 选项,请参阅更多内容。返回 self

# File activerecord/lib/active_record/persistence.rb, line 971
def increment!(attribute, by = 1, touch: nil)
  increment(attribute, by)
  change = public_send(attribute) - (public_send(:"#{attribute}_in_database") || 0)
  self.class.update_counters(id, attribute => change, touch: touch)
  public_send(:"clear_#{attribute}_change")
  self
end

new_record?()

如果此对象尚未保存,则返回 true,即数据库中尚未存在该对象的记录;否则,返回 false。

# File activerecord/lib/active_record/persistence.rb, line 665
def new_record?
  @new_record
end

persisted?()

如果记录是持久的,即它不是新记录并且没有被销毁,则返回 true,否则返回 false。

# File activerecord/lib/active_record/persistence.rb, line 688
def persisted?
  !(@new_record || @destroyed)
end

previously_new_record?()

如果此对象刚刚创建,则返回 true,即在上次更新或删除之前,该对象不存在于数据库中,并且 new_record? 将返回 true。

# File activerecord/lib/active_record/persistence.rb, line 672
def previously_new_record?
  @previously_new_record
end

previously_persisted?()

如果此对象以前是持久化的,但现在已被删除,则返回 true。

# File activerecord/lib/active_record/persistence.rb, line 677
def previously_persisted?
  !new_record? && destroyed?
end

reload(options = nil)

从数据库重新加载记录。

此方法按其主键(可手动分配)查找记录,并就地修改接收器

account = Account.new
# => #<Account id: nil, email: nil>
account.id = 1
account.reload
# Account Load (1.2ms)  SELECT "accounts".* FROM "accounts" WHERE "accounts"."id" = $1 LIMIT 1  [["id", 1]]
# => #<Account id: 1, email: 'account@example.com'>

属性从数据库重新加载,并清除缓存,尤其是关联缓存和QueryCache

如果记录不再存在于数据库中,则会引发ActiveRecord::RecordNotFound。否则,除了就地修改之外,该方法还返回self以方便使用。

可选的:lock标志选项允许您锁定重新加载的记录

reload(lock: true) # reload with pessimistic locking

重新加载通常用于测试套件中,以测试某些内容实际上已写入数据库,或者当某些操作修改数据库中的相应行但未修改内存中的对象时

assert account.deposit!(25)
assert_equal 25, account.credit        # check it is updated in memory
assert_equal 25, account.reload.credit # check it is also persisted

另一个常见的用例是乐观锁定处理

def with_optimistic_retry
  begin
    yield
  rescue ActiveRecord::StaleObjectError
    begin
      # Reload lock_version in particular.
      reload
    rescue ActiveRecord::RecordNotFound
      # If the record is gone there is nothing to do.
    else
      retry
    end
  end
end
# File activerecord/lib/active_record/persistence.rb, line 1069
def reload(options = nil)
  self.class.connection.clear_query_cache

  fresh_object = if apply_scoping?(options)
    _find_record((options || {}).merge(all_queries: true))
  else
    self.class.unscoped { _find_record(options) }
  end

  @association_cache = fresh_object.instance_variable_get(:@association_cache)
  @association_cache.each_value { |association| association.owner = self }
  @attributes = fresh_object.instance_variable_get(:@attributes)
  @new_record = false
  @previously_new_record = false
  self
end

save(**options)

保存模型。

如果模型是新的,则会在数据库中创建一个记录,否则会更新现有记录。

默认情况下,保存始终运行验证。如果其中任何一个失败,则操作将被取消,save返回false,并且记录将不会被保存。但是,如果您提供validate: false,则完全绕过验证。有关更多信息,请参阅ActiveRecord::Validations

默认情况下,save还会将updated_at/updated_on属性设置为当前时间。但是,如果您提供touch: false,则不会更新这些时间戳。

有一系列与save关联的回调。如果任何before_*回调抛出:abort,则操作将被取消,save返回false。有关更多详细信息,请参阅ActiveRecord::Callbacks

如果记录正在更新,则标记为只读的属性将被静默忽略。

# File activerecord/lib/active_record/persistence.rb, line 717
def save(**options, &block)
  create_or_update(**options, &block)
rescue ActiveRecord::RecordInvalid
  false
end

save!(**options)

保存模型。

如果模型是新的,则会在数据库中创建一个记录,否则会更新现有记录。

默认情况下,save! 始终运行验证。如果其中任何一个失败,则会引发 ActiveRecord::RecordInvalid,并且不会保存记录。但是,如果你提供 validate: false,则完全绕过验证。有关更多信息,请参阅 ActiveRecord::Validations

默认情况下,save! 还会将 updated_at/updated_on 属性设置为当前时间。但是,如果你提供 touch: false,则不会更新这些时间戳。

有一系列与 save! 关联的回调。如果任何 before_* 回调抛出 :abort,则取消操作,save! 引发 ActiveRecord::RecordNotSaved。有关更多详细信息,请参阅 ActiveRecord::Callbacks

如果记录正在更新,则标记为只读的属性将被静默忽略。

除非引发错误,否则返回 true。

# File activerecord/lib/active_record/persistence.rb, line 750
def save!(**options, &block)
  create_or_update(**options, &block) || raise(RecordNotSaved.new("Failed to save the record", self))
end

toggle(attribute)

attribute? 的布尔相反值分配给 attribute。因此,如果谓词返回 true,则属性将变为 false。此方法直接切换底层值,而不调用任何设置器。返回 self

示例

user = User.first
user.banned? # => false
user.toggle(:banned)
user.banned? # => true
# File activerecord/lib/active_record/persistence.rb, line 1008
def toggle(attribute)
  self[attribute] = !public_send("#{attribute}?")
  self
end

toggle!(attribute)

围绕 toggle 的包装器,用于保存记录。此方法与它的非感叹号版本不同,因为它通过属性设置器。保存不受验证检查的约束。如果可以保存记录,则返回 true

# File activerecord/lib/active_record/persistence.rb, line 1017
def toggle!(attribute)
  toggle(attribute).update_attribute(attribute, self[attribute])
end

touch(*names, time: nil)

将记录保存到更新时间/日期属性集为当前时间或指定时间。请注意,不会执行任何验证,只会执行after_touchafter_commitafter_rollback回调。

此方法可以传递属性名称和可选时间参数。如果传递属性名称,则会将它们与更新时间/日期属性一起更新。如果没有传递时间参数,则会使用当前时间作为默认值。

product.touch                         # updates updated_at/on with current time
product.touch(time: Time.new(2015, 2, 16, 0, 0, 0)) # updates updated_at/on with specified time
product.touch(:designed_at)           # updates the designed_at attribute and updated_at/on
product.touch(:started_at, :ended_at) # updates started_at, ended_at and updated_at/on attributes

如果与belongs_to一起使用,则touch将在关联对象上调用touch方法。

class Brake < ActiveRecord::Base
  belongs_to :car, touch: true
end

class Car < ActiveRecord::Base
  belongs_to :corporation, touch: true
end

# triggers @brake.car.touch and @brake.car.corporation.touch
@brake.touch

请注意,touch必须用于持久化对象,否则会引发ActiveRecordError。例如

ball = Ball.new
ball.touch(:updated_at)   # => raises ActiveRecordError
# File activerecord/lib/active_record/persistence.rb, line 1120
def touch(*names, time: nil)
  _raise_record_not_touched_error unless persisted?
  _raise_readonly_record_error if readonly?

  attribute_names = timestamp_attributes_for_update_in_model
  attribute_names = (attribute_names | names).map! do |name|
    name = name.to_s
    name = self.class.attribute_aliases[name] || name
    verify_readonly_attribute(name)
    name
  end

  unless attribute_names.empty?
    affected_rows = _touch_row(attribute_names, time)
    @_trigger_update_callback = affected_rows == 1
  else
    true
  end
end

update(attributes)

从传入的哈希中更新模型的属性并保存记录,所有内容都包含在一个事务中。如果对象无效,则保存操作将失败,并返回 false。

# File activerecord/lib/active_record/persistence.rb, line 890
def update(attributes)
  # The following transaction covers any possible database side-effects of the
  # attributes assignment. For example, setting the IDs of a child collection.
  with_transaction_returning_status do
    assign_attributes(attributes)
    save
  end
end

update!(attributes)

更新其接收器,就像update一样,但调用save!而不是save,因此如果记录无效且保存操作将失败,则会引发异常。

# File activerecord/lib/active_record/persistence.rb, line 901
def update!(attributes)
  # The following transaction covers any possible database side-effects of the
  # attributes assignment. For example, setting the IDs of a child collection.
  with_transaction_returning_status do
    assign_attributes(attributes)
    save!
  end
end

update_attribute(name, value)

更新单个属性并保存记录。这对于现有记录上的布尔标志特别有用。还要注意

  • 跳过验证。

  • 调用回调。

  • 如果存在 updated_at/updated_on 列,则会更新该列。

  • 更新此对象中所有已修改的属性。

如果将属性标记为只读,则此方法会引发ActiveRecord::ActiveRecordError

另请参阅update_column

# File activerecord/lib/active_record/persistence.rb, line 857
def update_attribute(name, value)
  name = name.to_s
  verify_readonly_attribute(name)
  public_send("#{name}=", value)

  save(validate: false)
end

update_attribute!(name, value)

更新单个属性并保存记录。这对于现有记录上的布尔标志特别有用。还要注意

  • 跳过验证。

  • 调用回调。

  • 如果存在 updated_at/updated_on 列,则会更新该列。

  • 更新此对象中所有已修改的属性。

如果将属性标记为只读,则此方法会引发ActiveRecord::ActiveRecordError

如果任何 before_* 回调抛出 :abort,操作将被取消,而 update_attribute! 将引发 ActiveRecord::RecordNotSaved。有关更多详细信息,请参阅 ActiveRecord::Callbacks

# File activerecord/lib/active_record/persistence.rb, line 879
def update_attribute!(name, value)
  name = name.to_s
  verify_readonly_attribute(name)
  public_send("#{name}=", value)

  save!(validate: false)
end

update_column(name, value)

相当于 update_columns(name => value)

# File activerecord/lib/active_record/persistence.rb, line 911
def update_column(name, value)
  update_columns(name => value)
end

update_columns(attributes)

直接在数据库中更新属性,发出 UPDATE SQL 语句,并将其设置在接收器中

user.update_columns(last_request_at: Time.current)

这是更新属性的最快方式,因为它直接进入数据库,但请注意,因此常规更新程序将被完全绕过。特别是

当对新对象调用此方法时,或当至少一个属性被标记为只读时,此方法会引发 ActiveRecord::ActiveRecordError

# File activerecord/lib/active_record/persistence.rb, line 931
def update_columns(attributes)
  raise ActiveRecordError, "cannot update a new record" if new_record?
  raise ActiveRecordError, "cannot update a destroyed record" if destroyed?
  _raise_readonly_record_error if readonly?

  attributes = attributes.transform_keys do |key|
    name = key.to_s
    name = self.class.attribute_aliases[name] || name
    verify_readonly_attribute(name) || name
  end

  update_constraints = _query_constraints_hash
  attributes = attributes.each_with_object({}) do |(k, v), h|
    h[k] = @attributes.write_cast_value(k, v)
    clear_attribute_change(k)
  end

  affected_rows = self.class._update_record(
    attributes,
    update_constraints
  )

  affected_rows == 1
end