Spec-Zone.ru › Ruby on Rails 6.0

module ActiveRecord::Persistence

Настойчивость Active Record

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

becomes(klass) Показать исходный код
# File activerecord/lib/active_record/persistence.rb, line 566
def becomes(klass)
  became = klass.allocate
  became.send(:initialize)
  became.instance_variable_set("@attributes", @attributes)
  became.instance_variable_set("@mutations_from_database", @mutations_from_database ||= nil)
  became.instance_variable_set("@new_record", new_record?)
  became.instance_variable_set("@destroyed", destroyed?)
  became.errors.copy!(errors)
  became
end

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

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

becomes!(klass) Показать исходный код
# File activerecord/lib/active_record/persistence.rb, line 583
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 715
def decrement(attribute, by = 1)
  increment(attribute, -by)
end

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

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

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

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

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

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

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

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

destroy() Показать исходный код
# File activerecord/lib/active_record/persistence.rb, line 531
def destroy
  _raise_readonly_record_error if readonly?
  destroy_associations
  @_trigger_destroy_callback = if persisted?
    destroy_row > 0
  else
    true
  end
  @destroyed = true
  freeze
end

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

Существует ряд обратных вызовов, связанных с destroy. Если обратный вызов before_destroy вызывает :abort, действие отменяется, и destroy возвращает false. См. ActiveRecord::Callbacks для получения дополнительной информации.

destroy!() Показать исходный код
# File activerecord/lib/active_record/persistence.rb, line 550
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 432
def destroyed?
  sync_with_transaction_state if @transaction_state&.finalized?
  @destroyed
end

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

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

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

increment!(attribute, by = 1, touch: nil) Показать исходный код
# File activerecord/lib/active_record/persistence.rb, line 704
def increment!(attribute, by = 1, touch: nil)
  increment(attribute, by)
  change = public_send(attribute) - (attribute_in_database(attribute.to_s) || 0)
  self.class.update_counters(id, attribute => change, touch: touch)
  clear_attribute_change(attribute) # eww
  self
end

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

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

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

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

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

reload(options = nil) Показать исходный код
# File activerecord/lib/active_record/persistence.rb, line 802
def reload(options = nil)
  self.class.connection.clear_query_cache

  fresh_object =
    if options && options[:lock]
      self.class.unscoped { self.class.lock(options[:lock]).find(id) }
    else
      self.class.unscoped { self.class.find(id) }
    end

  @attributes = fresh_object.instance_variable_get("@attributes")
  @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'>

Атрибуты перезагружаются из базы данных, и кеши очищаются, в частности кеш ассоциаций и 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(*args) Показать исходный код
# File activerecord/lib/active_record/persistence.rb, line 469
def save(*args, &block)
  create_or_update(*args, &block)
rescue ActiveRecord::RecordInvalid
  false
end

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

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

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

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

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

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

save!(*args) Показать исходный код
# File activerecord/lib/active_record/persistence.rb, line 502
def save!(*args, &block)
  create_or_update(*args, &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.

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

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

toggle(attribute) Показать исходный код
# File activerecord/lib/active_record/persistence.rb, line 741
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 750
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 851
    def touch(*names, time: nil)
      unless persisted?
        raise ActiveRecordError, <<-MSG.squish
          cannot touch on a new or destroyed record object. Consider using
          persisted?, new_record?, or destroyed? before touching
        MSG
      end

      attribute_names = timestamp_attributes_for_update_in_model
      attribute_names |= names.map!(&:to_s).map! { |name|
        self.class.attribute_aliases[name] || name
      }

      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.

Этот метод может принимать имена атрибутов и необязательный аргумент time. Если переданы имена атрибутов, они обновляются вместе с атрибутами updated_at/on. Если аргумент time не передан, используется текущее время по умолчанию.

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 616
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
update!(attributes) Показать исходный код
# File activerecord/lib/active_record/persistence.rb, line 630
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_attributes!
update_attribute(name, value) Показать исходный код
# File activerecord/lib/active_record/persistence.rb, line 605
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, если атрибут помечен как только для чтения (readonly).

См. также update_column.

update_attributes(attributes)
Псевдоним для: update
update_attributes!(attributes)
Псевдоним для: update!
update_column(name, value) Показать исходный код
# File activerecord/lib/active_record/persistence.rb, line 643
def update_column(name, value)
  update_columns(name => value)
end

Эквивалент update_columns(name => value).

update_columns(attributes) Показать исходный код
# File activerecord/lib/active_record/persistence.rb, line 663
def update_columns(attributes)
  raise ActiveRecordError, "cannot update a new record" if new_record?
  raise ActiveRecordError, "cannot update a destroyed record" if destroyed?

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

  attributes.each_key do |key|
    verify_readonly_attribute(key)
  end

  id_in_database = self.id_in_database
  attributes.each do |k, v|
    write_attribute_without_type_cast(k, v)
  end

  affected_rows = self.class._update_record(
    attributes,
    @primary_key => id_in_database
  )

  affected_rows == 1
end

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

user.update_columns(last_request_at: Time.current)

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

  • Валидация пропускается.

  • Колбеки пропускаются.

  • updated_at/updated_on не обновляются.

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

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

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

Spec-Zone.ru

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