Spec-Zone.ru › Ruby on Rails 8.1

модуль ActiveRecord::Persistence

Сохранение записей Active Record

Открытые методы экземпляра

becomes (klass) Показать исходный код
# 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

Возвращает экземпляр указанного klass с атрибутами текущей записи. Это особенно полезно при использовании структур наследования с одной таблицей (STI), когда требуется, чтобы подкласс выглядел как суперкласс. Это можно использовать вместе с идентификацией записей в Action Pack, чтобы, например, Client < Company мог выполнить что-то вроде рендеринга partial: @client.becomes(Company) и отобразить этот экземпляр с помощью частичного шаблона companies/company вместо clients/client.

Примечание: новый экземпляр будет ссылаться на тот же набор атрибутов, что и исходный класс. Поэтому значение столбца STI останется прежним. Любое изменение атрибутов одного из экземпляров повлияет на оба экземпляра. Сюда входит и инициализация атрибутов, выполненная новым экземпляром.

Если требуется изменить и столбец STI, используйте вместо этого becomes!.

becomes! (klass) Показать исходный код
# 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

Обёртка над becomes, которая также изменяет значение столбца STI экземпляра. Это особенно полезно, если требуется сохранить изменённый класс в базе данных.

Примечание: значение столбца STI старого экземпляра также изменится, поскольку оба объекта используют один и тот же набор атрибутов.

decrement (attribute, by = 1) Показать исходный код
# File activerecord/lib/active_record/persistence.rb, line 686
def decrement(attribute, by = 1)
  increment(attribute, -by)
end

Инициализирует attribute нулём, если nil, и вычитает значение, переданное в качестве by (по умолчанию — 1). Уменьшение выполняется непосредственно для базового атрибута, сеттер не вызывается. Имеет смысл только для числовых атрибутов. Возвращает self.

decrement! (attribute, by = 1, touch: nil) Показать исходный код
# File activerecord/lib/active_record/persistence.rb, line 696
def decrement!(attribute, by = 1, touch: nil)
  increment!(attribute, -by, touch: touch)
end

Обёртка над decrement, которая записывает обновление в базу данных. Обновляется только attribute; сама запись не сохраняется. Это означает, что все остальные изменённые атрибуты останутся помеченными как изменённые. Validations и обратные вызовы пропускаются. Поддерживает параметр touch метода update_counters; подробнее см. его описание. Возвращает self.

delete () Показать исходный код
# File activerecord/lib/active_record/persistence.rb, line 439
def delete
  _delete_row if persisted?
  @destroyed = true
  @previously_new_record = false
  freeze
end

Удаляет запись из базы данных и замораживает этот экземпляр, показывая, что изменения вносить не следует, поскольку их невозможно сохранить. Возвращает замороженный экземпляр.

Строка просто удаляется с помощью SQL-оператора DELETE по первичному ключу записи, обратные вызовы не выполняются.

Обратите внимание, что будут также удалены записи, помеченные как #readonly?.

Чтобы обеспечить выполнение обратных вызовов before_destroy и after_destroy объекта или любых параметров ассоциации :dependent, используйте destroy.

destroy () Показать исходный код
# 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 связан ряд обратных вызовов. Если обратный вызов before_destroy выбрасывает :abort, действие отменяется, а destroy возвращает false. Подробнее см. в разделе ActiveRecord::Callbacks.

destroy! () Показать исходный код
# File activerecord/lib/active_record/persistence.rb, line 469
def destroy!
  destroy || _raise_record_not_destroyed
end

Удаляет запись из базы данных и замораживает этот экземпляр, показывая, что изменения вносить не следует, поскольку их невозможно сохранить.

С методом destroy! связан ряд обратных вызовов. Если обратный вызов before_destroy выбрасывает :abort, действие отменяется, а destroy! вызывает исключение ActiveRecord::RecordNotDestroyed. Подробнее см. в разделе ActiveRecord::Callbacks.

destroyed? () Показать исходный код
# File activerecord/lib/active_record/persistence.rb, line 355
def destroyed?
  @destroyed
end

Возвращает true, если этот объект был уничтожен, иначе возвращает false.

increment (attribute, by = 1) Показать исходный код
# File activerecord/lib/active_record/persistence.rb, line 656
def increment(attribute, by = 1)
  self[attribute] ||= 0
  self[attribute] += by
  self
end

Инициализирует attribute нулём, если nil, и прибавляет значение, переданное в качестве by (по умолчанию — 1). Увеличение выполняется непосредственно для базового атрибута, сеттер не вызывается. Имеет смысл только для числовых атрибутов. Возвращает self.

increment! (attribute, by = 1, touch: nil) Показать исходный код
# 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

Обёртка над increment, которая записывает обновление в базу данных. Обновляется только attribute; сама запись не сохраняется. Это означает, что все остальные изменённые атрибуты останутся помеченными как изменённые. Validations и обратные вызовы пропускаются. Поддерживает параметр touch метода update_counters; подробнее см. его описание.

При вызове для новых объектов или если хотя бы один из атрибутов помечен как доступный только для чтения, этот метод вызывает исключение ActiveRecord::ActiveRecordError.

Возвращает self.

new_record? () Показать исходный код
# File activerecord/lib/active_record/persistence.rb, line 338
def new_record?
  @new_record
end

Возвращает true, если этот объект ещё не сохранялся, то есть запись для объекта ещё не существует в базе данных; в противном случае возвращает false.

persisted? () Показать исходный код
# File activerecord/lib/active_record/persistence.rb, line 361
def persisted?
  !(@new_record || @destroyed)
end

Возвращает true, если запись сохранена, то есть она не является новой и не была уничтожена; в противном случае возвращает false.

previously_new_record? () Показать исходный код
# File activerecord/lib/active_record/persistence.rb, line 345
def previously_new_record?
  @previously_new_record
end

Возвращает true, если этот объект был только что создан, то есть до последнего обновления или удаления объект не существовал в базе данных, и метод new_record? вернул бы true.

previously_persisted? () Показать исходный код
# File activerecord/lib/active_record/persistence.rb, line 350
def previously_persisted?
  !new_record? && destroyed?
end

Возвращает true, если этот объект ранее был сохранён, но теперь удалён.

reload (options = nil) Показать исходный код
# 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

Повторно загружает запись из базы данных.

Этот метод находит запись по её первичному ключу (который можно назначить вручную) и изменяет получатель на месте:

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
save(**options) Показать исходный код
# File activerecord/lib/active_record/persistence.rb, line 390
def save(**options, &block)
  create_or_update(**options, &block)
rescue ActiveRecord::RecordInvalid
  false
end

Сохраняет модель.

Если модель новая, в базе данных создаётся запись; в противном случае обновляется существующая запись.

По умолчанию при сохранении всегда выполняются проверки. Если какая-либо из них завершается неудачно, действие отменяется, метод save возвращает false, а запись не сохраняется. Однако, если передать validate: false, все проверки будут пропущены. Подробнее см. в разделе ActiveRecord::Validations.

По умолчанию метод save также устанавливает атрибуты updated_at/updated_on в текущее время. Однако если передать touch: false, эти временные метки обновляться не будут.

С методом save связан ряд обратных вызовов. Если какой-либо из обратных вызовов before_* выбрасывает :abort, действие отменяется, а save возвращает false. Подробнее см. в разделе ActiveRecord::Callbacks.

Attributes, помеченные как доступные только для чтения, при обновлении записи молча игнорируются.

save!(**options) Показать исходный код
# 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

Сохраняет модель.

Если модель новая, в базе данных создаётся запись; в противном случае обновляется существующая запись.

По умолчанию метод save! всегда выполняет проверки. Если какая-либо из них завершается неудачно, вызывается исключение ActiveRecord::RecordInvalid, а запись не сохраняется. Однако если передать validate: false, все проверки будут пропущены. Подробнее см. в разделе ActiveRecord::Validations.

По умолчанию метод save! также устанавливает атрибуты updated_at/updated_on в текущее время. Однако если передать touch: false, эти временные метки обновляться не будут.

С методом save! связан ряд обратных вызовов. Если какой-либо из обратных вызовов before_* выбрасывает :abort, действие отменяется, а save! вызывает исключение ActiveRecord::RecordNotSaved. Подробнее см. в разделе ActiveRecord::Callbacks.

Attributes, помеченные как доступные только для чтения, при обновлении записи молча игнорируются.

Если исключение не возникло, возвращает true.

toggle (attribute) Показать исходный код
# File activerecord/lib/active_record/persistence.rb, line 712
def toggle(attribute)
  self[attribute] = !public_send("#{attribute}?")
  self
end

Присваивает attribute логическое значение, противоположное attribute?. Поэтому, если предикат возвращает true, атрибут станет false. Этот метод переключает непосредственно базовое значение, не вызывая сеттер. Возвращает self.

Пример:

user = User.first
user.banned? # => false
user.toggle(:banned)
user.banned? # => true
toggle! (attribute) Показать исходный код
# File activerecord/lib/active_record/persistence.rb, line 721
def toggle!(attribute)
  toggle(attribute).update_attribute(attribute, self[attribute])
end

Обёртка над toggle, которая сохраняет запись. Этот метод отличается от версии без восклицательного знака тем, что вызывает сеттер атрибута. Сохранение не проходит проверку валидации. Возвращает true, если запись удалось сохранить.

touch (*names, time: nil) Показать исходный код
# 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

Сохраняет запись, устанавливая атрибуты updated_at/on в текущее время или в указанное время. Обратите внимание, что проверка не выполняется, а запускаются только обратные вызовы after_touch, after_commit и after_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
update (attributes) Показать исходный код
# 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

Обновляет атрибуты модели данными из переданного хеша и сохраняет запись; всё это выполняется в рамках транзакции. Если объект недопустим, сохранение завершится неудачно и будет возвращено false.

update! (attributes) Показать исходный код
# 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, но вызывает save! вместо save, поэтому, если запись недопустима и сохранить её не удаётся, возникает исключение.

update_attribute (name, value) Показать исходный код
# 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

Обновляет один атрибут и сохраняет запись. Это особенно полезно для логических флагов в существующих записях. Также обратите внимание, что

  • Проверка не выполняется.

  • Обратные вызовы вызываются.

  • Столбец updated_at/updated_on обновляется, если он существует.

  • Обновляются все атрибуты этого объекта, помеченные как изменённые.

Если атрибут помечен как доступный только для чтения, этот метод вызывает исключение ActiveRecord::ActiveRecordError.

См. также update_column.

update_attribute! (name, value) Показать исходный код
# 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

Обновляет один атрибут и сохраняет запись. Это особенно полезно для логических флагов в существующих записях. Также обратите внимание, что

  • Проверка не выполняется.

  • Обратные вызовы вызываются.

  • Столбец updated_at/updated_on обновляется, если он существует.

  • Обновляются все атрибуты этого объекта, помеченные как изменённые.

Если атрибут помечен как доступный только для чтения, этот метод вызывает исключение ActiveRecord::ActiveRecordError.

Если какой-либо из обратных вызовов before_* выбрасывает :abort, действие отменяется, а update_attribute! вызывает исключение ActiveRecord::RecordNotSaved. Подробнее см. в разделе ActiveRecord::Callbacks.

update_column (name, value, touch: nil) Показать исходный код
# 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(name => value).

update_columns (attributes) Показать исходный код
# 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

Обновляет атрибуты непосредственно в базе данных, выполняя SQL-оператор UPDATE, и устанавливает их для получателя:

user.update_columns(last_request_at: Time.current)

Это самый быстрый способ обновить атрибуты, поскольку изменения сразу записываются в базу данных. Однако учитывайте, что при этом стандартные процедуры обновления полностью обходятся. В частности:

  • Проверки не выполняются.

  • Обратные вызовы пропускаются.

  • Атрибуты updated_at/updated_on обновляются, если параметр touch имеет значение true.

  • Однако сериализация атрибутов выполняется по тем же правилам, что и в ActiveRecord::Relation#update_all

При вызове для новых объектов или если хотя бы один из атрибутов помечен как доступный только для чтения, этот метод вызывает исключение ActiveRecord::ActiveRecordError.

Параметры

  • Параметр :touch — обновлять столбцы временных меток при обновлении.

  • Если переданы имена атрибутов, они обновляются вместе с атрибутами updated_at/updated_on.

Примеры

# 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)

© 2004–2021 David Heinemeier Hansson
Licensed under the MIT License.

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API