модуль ActiveRecord::Persistence
Персистентность Active Record
Публичные методы экземпляра
# File activerecord/lib/active_record/persistence.rb, line 220
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 делать что-то вроде отображения partial: @client.becomes(Company), чтобы отобразить этот экземпляр, используя частичный шаблон companies/company вместо clients/client.
Примечание: новый экземпляр будет совместно использовать ссылку на те же атрибуты, что и исходный класс. Поэтому значение столбца sti останется тем же самым. Любое изменение атрибутов в любом из экземпляров повлияет на оба экземпляра. Если вы также хотите изменить столбец sti, используйте becomes! вместо этого.
# File activerecord/lib/active_record/persistence.rb, line 237
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 356 def decrement(attribute, by = 1) increment(attribute, -by) end
Инициализирует attribute в ноль, если nil и вычитает значение, переданное как by (по умолчанию 1). Уменьшение выполняется непосредственно по базовому атрибуту, вызыватель не вызывается. Имеет смысл только для числовых атрибутов. Возвращает self.
# File activerecord/lib/active_record/persistence.rb, line 364 def decrement!(attribute, by = 1) increment!(attribute, -by) end
Обертка вокруг decrement, которая записывает обновление в базу данных. Обновляется только attribute; сама запись не сохраняется. Это означает, что любые другие измененные атрибуты останутся измененными. Валидация и обратные вызовы пропускаются. Возвращает self.
# File activerecord/lib/active_record/persistence.rb, line 175 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 188
def destroy
raise ReadOnlyRecord, "#{self.class} is marked as readonly" if readonly?
destroy_associations
self.class.connection.add_transaction_record(self)
destroy_row if persisted?
@destroyed = true
freeze
end Удаляет запись в базе данных и замораживает этот экземпляр, чтобы отразить, что изменения не должны вноситься (поскольку их нельзя сохранить).
Существует ряд обратных вызовов, связанных с destroy. Если обратный вызов before_destroy вызывает исключение :abort, действие отменяется, и destroy возвращает false. См. ActiveRecord::Callbacks для получения дополнительной информации.
# File activerecord/lib/active_record/persistence.rb, line 204 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 335 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 345 def increment!(attribute, by = 1) increment(attribute, by) change = public_send(attribute) - (attribute_was(attribute.to_s) || 0) self.class.update_counters(id, attribute => change) clear_attribute_change(attribute) # eww self end
Обертка вокруг increment, которая записывает обновление в базу данных. Обновляется только attribute; сама запись не сохраняется. Это означает, что любые другие измененные атрибуты останутся измененными. Валидация и обратные вызовы пропускаются. Возвращает 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 441
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 159
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.
Атрибуты, помеченные как только для чтения, молча игнорируются, если запись обновляется.
# File activerecord/lib/active_record/persistence.rb, line 380
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 389 def toggle!(attribute) toggle(attribute).update_attribute(attribute, self[attribute]) end
Обертка над toggle, которая сохраняет запись. Этот метод отличается от своей версии без восклицательного знака тем, что он пропускает метод-сеттер атрибута. Сохранение не подчиняется проверкам валидации. Возвращает true, если запись была сохранена.
# File activerecord/lib/active_record/persistence.rb, line 490
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
time ||= current_time_from_proper_timezone
attributes = timestamp_attributes_for_update_in_model
attributes.concat(names)
unless attributes.empty?
changes = {}
attributes.each do |column|
column = column.to_s
changes[column] = write_attribute(column, time)
end
clear_attribute_changes(changes.keys)
primary_key = self.class.primary_key
scope = self.class.unscoped.where(primary_key => _read_attribute(primary_key))
if locking_enabled?
locking_column = self.class.locking_column
scope = scope.where(locking_column => _read_attribute(locking_column))
changes[locking_column] = increment_lock
end
result = scope.update_all(changes) == 1
if !result && locking_enabled?
raise ActiveRecord::StaleObjectError.new(self, "touch")
end
result
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 270
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 283
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 259
def update_attribute(name, value)
name = name.to_s
verify_readonly_attribute(name)
public_send("#{name}=", value)
changed? ? save(validate: false) : true
end Обновляет отдельный атрибут и сохраняет запись. Это особенно полезно для логических флагов существующих записей. Также обратите внимание, что:
-
Валидация пропускается.
-
Вызываются обратные вызовы.
-
Столбец updated_at/updated_on обновляется, если такой столбец существует.
-
Обновляются все грязные атрибуты в этом объекте.
Этот метод генерирует ActiveRecord::ActiveRecordError, если атрибут помечен как только для чтения.
См. также update_column.
# File activerecord/lib/active_record/persistence.rb, line 295 def update_column(name, value) update_columns(name => value) end
Эквивалентно update_columns(name => value).
# File activerecord/lib/active_record/persistence.rb, line 315
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 Обновляет атрибуты непосредственно в базе данных, выполняя SQL-запрос UPDATE, и устанавливает их в получателе:
user.update_columns(last_request_at: Time.current)
Это самый быстрый способ обновления атрибутов, поскольку он напрямую обращается к базе данных, но учтите, что стандартные процедуры обновления полностью пропущены. В частности:
-
Валидация пропускается.
-
Обратные вызовы пропускаются.
-
updated_at/updated_onне обновляются. -
Однако, атрибуты сериализуются по тем же правилам, что и в ActiveRecord::Relation#update_all.
Этот метод генерирует ActiveRecord::ActiveRecordError при вызове для новых объектов или когда хотя бы один из атрибутов помечен как только для чтения.
© 2004–2018 David Heinemeier Hansson
Licensed under the MIT License.