module ActiveRecord::Persistence
Настойчивость Active Record
Публичные методы экземпляра
# File activerecord/lib/active_record/persistence.rb, line 226
def becomes(klass)
became = klass.new
became.instance_variable_set("@attributes", @attributes)
became.instance_variable_set("@mutation_tracker", @mutation_tracker) if defined?(@mutation_tracker)
became.instance_variable_set("@changed_attributes", attributes_changed_by_setter)
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! вместо этого.
# File activerecord/lib/active_record/persistence.rb, line 243
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 старого экземпляра также будет изменено, поскольку оба объекта используют один и тот же набор атрибутов.
# File activerecord/lib/active_record/persistence.rb, line 364 def decrement(attribute, by = 1) increment(attribute, -by) end
Инициализирует attribute в ноль, если nil и вычитает значение, переданное как by (по умолчанию 1). Уменьшение выполняется непосредственно по базовому атрибуту, сеттер не вызывается. Имеет смысл только для числовых атрибутов. Возвращает self.
# File activerecord/lib/active_record/persistence.rb, line 374 def decrement!(attribute, by = 1, touch: nil) increment!(attribute, -by, touch: touch) end
Обёртка вокруг decrement, которая записывает обновление в базу данных. Обновляется только attribute; сама запись не сохраняется. Это означает, что любые другие измененные атрибуты всё ещё будут изменены. Проверки и обратные вызовы пропускаются. Поддерживает параметр `touch` из update_counters, см. подробнее. Возвращает self.
# File activerecord/lib/active_record/persistence.rb, line 177 def delete self.class.delete(id) if persisted? @destroyed = true freeze end
Удаляет запись в базе данных и замораживает этот экземпляр, чтобы отразить, что изменения не должны вноситься (поскольку их нельзя сохранить). Возвращает замороженный экземпляр.
Строка просто удаляется с помощью оператора SQL DELETE по первичному ключу записи, и обратные вызовы не выполняются.
Обратите внимание, что это также удалит записи, помеченные как #readonly?.
Чтобы обеспечить соблюдение before_destroy и after_destroy обратных вызовов объекта или любых параметров ассоциации :dependent, используйте #destroy.
# File activerecord/lib/active_record/persistence.rb, line 190
def destroy
_raise_readonly_record_error if readonly?
destroy_associations
self.class.connection.add_transaction_record(self)
@_trigger_destroy_callback = if persisted?
destroy_row > 0
else
true
end
@destroyed = true
freeze
end Удаляет запись в базе данных и замораживает этот экземпляр, чтобы отразить, что изменения не должны вноситься (поскольку их нельзя сохранить).
Существует ряд обратных вызовов, связанных с destroy. Если обратный вызов before_destroy вызывает :abort, действие отменяется, и destroy возвращает false. Более подробную информацию см. в разделе ActiveRecord::Callbacks.
# File activerecord/lib/active_record/persistence.rb, line 210 def destroy! destroy || _raise_record_not_destroyed end
Удаляет запись в базе данных и замораживает этот экземпляр, чтобы отразить, что изменения не должны вноситься (поскольку их нельзя сохранить).
Существует ряд обратных вызовов, связанных с destroy!. Если обратный вызов before_destroy вызывает :abort, действие отменяется, и destroy! вызывает ActiveRecord::RecordNotDestroyed. Более подробную информацию см. в разделе ActiveRecord::Callbacks.
# File activerecord/lib/active_record/persistence.rb, line 91 def destroyed? sync_with_transaction_state @destroyed end
Возвращает true, если этот объект был уничтожен, в противном случае возвращает false.
# File activerecord/lib/active_record/persistence.rb, line 341 def increment(attribute, by = 1) self[attribute] ||= 0 self[attribute] += by self end
Инициализирует attribute в ноль, если nil и добавляет значение, переданное как by (по умолчанию 1). Увеличение выполняется непосредственно по базовому атрибуту, сеттер не вызывается. Имеет смысл только для числовых атрибутов. Возвращает self.
# File activerecord/lib/active_record/persistence.rb, line 353 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.
# File activerecord/lib/active_record/persistence.rb, line 85 def new_record? sync_with_transaction_state @new_record end
Возвращает true, если этот объект ещё не сохранён, то есть запись для объекта ещё не существует в базе данных; в противном случае возвращает false.
# File activerecord/lib/active_record/persistence.rb, line 98 def persisted? sync_with_transaction_state !(@new_record || @destroyed) end
Возвращает true, если запись сохранена, т. е. это не новая запись и она не была уничтожена, в противном случае возвращает false.
# File activerecord/lib/active_record/persistence.rb, line 451
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
# File activerecord/lib/active_record/persistence.rb, line 128 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, игнорируются, если запись обновляется.
# File activerecord/lib/active_record/persistence.rb, line 161
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.
# File activerecord/lib/active_record/persistence.rb, line 390
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
# File activerecord/lib/active_record/persistence.rb, line 399 def toggle!(attribute) toggle(attribute).update_attribute(attribute, self[attribute]) end
Обёртка над toggle, которая сохраняет запись. Этот метод отличается от своей версии без bang тем, что он проходит через сетер атрибута. Сохранение не подвержено проверке валидации. Возвращает true если запись была сохранена.
# File activerecord/lib/active_record/persistence.rb, line 500
def touch(*names, time: nil)
unless persisted?
raise ActiveRecordError, " cannot touch on a new or destroyed record object. Consider using
persisted?, new_record?, or destroyed? before touching
".squish
end
attribute_names = timestamp_attributes_for_update_in_model
attribute_names.concat(names)
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
# File activerecord/lib/active_record/persistence.rb, line 276
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.
# File activerecord/lib/active_record/persistence.rb, line 289
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, поэтому генерируется исключение, если запись невалидна, и сохранение завершится ошибкой.
# File activerecord/lib/active_record/persistence.rb, line 265
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.
# File activerecord/lib/active_record/persistence.rb, line 301 def update_column(name, value) update_columns(name => value) end
Эквивалентно update_columns(name => value).
# File activerecord/lib/active_record/persistence.rb, line 321
def update_columns(attributes)
raise ActiveRecordError, "cannot update a new record" if new_record?
raise ActiveRecordError, "cannot update a destroyed record" if destroyed?
attributes.each_key do |key|
verify_readonly_attribute(key.to_s)
end
updated_count = self.class.unscoped.where(self.class.primary_key => id).update_all(attributes)
attributes.each do |k, v|
raw_write_attribute(k, v)
end
updated_count == 1
end Обновляет атрибуты напрямую в базе данных, выполняя оператор UPDATE SQL, и устанавливает их в получателе:
user.update_columns(last_request_at: Time.current)
Это самый быстрый способ обновления атрибутов, так как он работает напрямую с базой данных, но учтите, что в результате обычные процедуры обновления полностью пропускаются. В частности:
-
Валидация пропускается.
-
Колбеки пропускаются.
-
updated_at/updated_onне обновляются. -
Однако, атрибуты сериализуются по тем же правилам, что и ActiveRecord::Relation#update_all
Этот метод генерирует исключение ActiveRecord::ActiveRecordError, если вызывается для новых объектов или если хотя бы один из атрибутов помечен как readonly.
© 2004–2018 David Heinemeier Hansson
Licensed under the MIT License.