跳至内容 跳至搜索

Active Record Persistence

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

实例公共方法

becomes(klass)

返回指定klass的一个实例,并具有当前记录的属性。这在与单表继承(STI)结构结合使用时非常有用,当你希望一个子类表现得像父类一样。这可以与Action Pack中的记录识别一起使用,例如,允许Client < Company执行类似render partial: @client.becomes(Company)的操作,以便使用companies/company的部分而不是clients/client来渲染该实例。

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

如果你也想更改STI列,请改用becomes!

# File activerecord/lib/active_record/persistence.rb, line 487
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(:@previously_new_record, previously_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 509
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 686
def decrement(attribute, by = 1)
  increment(attribute, -by)
end

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

decrement的包装器,它将更新写入数据库。仅更新attribute;记录本身未保存。这意味着任何其他修改过的属性仍然是脏的。Validations和回调将被跳过。支持update_counters中的touch选项,请参见该方法了解更多。返回self

# File activerecord/lib/active_record/persistence.rb, line 696
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 439
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 453
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 469
def destroy!
  destroy || _raise_record_not_destroyed
end

destroyed?()

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

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

increment(attribute, by = 1)

如果attributenil,则将其初始化为零,然后加上传递的by值(默认为1)。递增操作直接在底层属性上执行,不调用setter。仅对数字型属性有意义。返回self

# File activerecord/lib/active_record/persistence.rb, line 656
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选项,请参见该方法了解更多。

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

返回 self

# File activerecord/lib/active_record/persistence.rb, line 672
def increment!(attribute, by = 1, touch: nil)
  raise ActiveRecordError, "cannot update a new record" if new_record?
  raise ActiveRecordError, "cannot update a destroyed record" if destroyed?

  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 338
def new_record?
  @new_record
end

persisted?()

如果记录已持久化,则返回true,即它不是新记录且未被销毁,否则返回false。

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

previously_new_record?()

如果此对象刚刚被创建,则返回true——也就是说,在上次更新或删除之前,该对象在数据库中不存在,并且new_record?会返回true。

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

previously_persisted?()

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

# File activerecord/lib/active_record/persistence.rb, line 350
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'>

Attributes将从数据库重新加载,缓存将被清除,特别是关联缓存和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 773
def reload(options = nil)
  self.class.connection_pool.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始终运行验证。如果任何验证失败,操作将被取消,save将返回false,并且记录将不会被保存。但是,如果您提供validate: false,则会完全绕过验证。有关更多信息,请参阅ActiveRecord::Validations

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

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

Attributes标记为只读的,如果在更新记录时,它们将被静默忽略。

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

Attributes标记为只读的,如果在更新记录时,它们将被静默忽略。

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

# File activerecord/lib/active_record/persistence.rb, line 423
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。此方法直接切换底层值而不调用任何setter。返回self

示例

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

toggle!(attribute)

toggle的包装器,它会保存记录。此方法与其非bang版本不同之处在于它通过了属性setter。保存不受验证检查。如果记录能够保存,则返回true

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

touch(*names, time: nil)

保存记录,并将updated_at/on属性设置为当前时间或指定时间。请注意,不会执行任何验证,并且只执行after_touchafter_commitafter_rollback回调。

此方法可以传递属性名称和可选的时间参数。如果传递了属性名称,它们将与updated_at/on属性一起更新。如果未传递时间参数,则默认使用当前时间。

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 824
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 564
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 575
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 531
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 553
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, touch: nil)

等同于update_columns(name => value)

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

update_columns(attributes)

直接在数据库中更新属性,执行UPDATE SQL语句,并将这些属性设置在接收者中。

user.update_columns(last_request_at: Time.current)

这是更新属性的最快方式,因为它直接操作数据库,但请注意,因此 regular update procedures will be completely bypassed. In particular

  • 验证将被跳过。

  • 回调将被跳过。

  • 如果touch选项设置为true,则会更新updated_at/updated_on

  • 但是,属性的序列化规则与ActiveRecord::Relation#update_all相同。

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

Parameters

  • :touch选项 - 更新时触碰时间戳列。

  • 如果传递了属性名称,它们将与updated_at/updated_on属性一起更新。

Examples

# Update a single attribute.
user.update_columns(last_request_at: Time.current)

# Update with touch option.
user.update_columns(last_request_at: Time.current, touch: true)
# File activerecord/lib/active_record/persistence.rb, line 619
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

  touch = attributes.delete("touch")
  if touch
    names = touch if touch != true
    names = Array.wrap(names)
    options = names.extract_options!
    touch_updates = self.class.touch_attributes_with_time(*names, **options)
    attributes.with_defaults!(touch_updates) unless touch_updates.empty?
  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