module ActiveRecord::Persistence
def _create_record(attribute_names = self.attribute_names)
Creates a record with values matching those of the instance attributes
def _create_record(attribute_names = self.attribute_names) attribute_names = attributes_for_create(attribute_names) new_id = self.class._insert_record( attributes_with_values(attribute_names) ) self.id ||= new_id if @primary_key @new_record = false @previously_new_record = true yield(self) if block_given? id end
def _delete_row
def _delete_row self.class._delete_record(_query_constraints_hash) end
def _find_record(options)
def _find_record(options) if options && options[:lock] self.class.preload(strict_loaded_associations).lock(options[:lock]).find(id) else self.class.preload(strict_loaded_associations).find(id) end end
def _query_constraints_hash
def _query_constraints_hash { @primary_key => id_in_database } end
def _raise_readonly_record_error
def _raise_readonly_record_error raise ReadOnlyRecord, "#{self.class} is marked as readonly" end
def _raise_record_not_destroyed
def _raise_record_not_destroyed @_association_destroy_exception ||= nil key = self.class.primary_key raise @_association_destroy_exception || RecordNotDestroyed.new("Failed to destroy #{self.class} with #{key}=#{send(key)}", self) ensure @_association_destroy_exception = nil end
def _raise_record_not_touched_error
def _raise_record_not_touched_error raise ActiveRecordError, <<~MSG.squish Cannot touch on a new or destroyed record object. Consider using persisted?, new_record?, or destroyed? before touching. MSG end
def _touch_row(attribute_names, time)
def _touch_row(attribute_names, time) time ||= current_time_from_proper_timezone attribute_names.each do |attr_name| _write_attribute(attr_name, time) end _update_row(attribute_names, "touch") end
def _update_record(attribute_names = self.attribute_names)
Updates the associated record with values matching those of the instance attributes.
def _update_record(attribute_names = self.attribute_names) attribute_names = attributes_for_update(attribute_names) if attribute_names.empty? affected_rows = 0 @_trigger_update_callback = true else affected_rows = _update_row(attribute_names) @_trigger_update_callback = affected_rows == 1 end @previously_new_record = false yield(self) if block_given? affected_rows end
def _update_row(attribute_names, attempted_action = "update")
def _update_row(attribute_names, attempted_action = "update") self.class._update_record( attributes_with_values(attribute_names), _query_constraints_hash ) end
def apply_scoping?(options)
def apply_scoping?(options) !(options && options[:unscoped]) && (self.class.default_scopes?(all_queries: true) || self.class.global_current_scope) end
def becomes(klass)
Any change to the attributes on either instance will affect both instances.
Therefore the STI column value will still be the same.
Note: The new instance will share a link to the same attributes as the original class.
instance using the companies/company partial instead of clients/client.
like render partial: @client.becomes(Company) to render that
Action Pack to allow, say, Client < Company to do something
superclass. This can be used along with record identification in
inheritance (STI) structures where you want a subclass to appear as the
current record. This is mostly useful in relation to single table
Returns an instance of the specified +klass+ with the attributes of the
def becomes(klass) became = klass.allocate became.send(:initialize) do |becoming| 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(:@destroyed, destroyed?) becoming.errors.copy!(errors) end became end
def becomes!(klass)
Note: The old instance's STI column value will be changed too, as both objects
database.
This is especially useful if you want to persist the changed class in your
Wrapper around #becomes that also changes the instance's STI column value.
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
def belongs_to_touch_method
The name of the method used to touch a +belongs_to+ association when the
def belongs_to_touch_method :touch end
def create_or_update(**, &block)
def create_or_update(**, &block) _raise_readonly_record_error if readonly? return false if destroyed? result = new_record? ? _create_record(&block) : _update_record(&block) result != false end
def decrement(attribute, by = 1)
The decrement is performed directly on the underlying attribute, no setter is invoked.
Initializes +attribute+ to zero if +nil+ and subtracts the value passed as +by+ (default is 1).
def decrement(attribute, by = 1) increment(attribute, -by) end
def decrement!(attribute, by = 1, touch: nil)
+update_counters+, see that for more.
Validations and callbacks are skipped. Supports the +touch+ option from
This means that any other modified attributes will still be dirty.
Only +attribute+ is updated; the record itself is not saved.
Wrapper around #decrement that writes the update to the database.
def decrement!(attribute, by = 1, touch: nil) increment!(attribute, -by, touch: touch) end
def delete
callbacks or any :dependent association
To enforce the object's +before_destroy+ and +after_destroy+
Note that this will also delete records marked as {#readonly?}[rdoc-ref:Core#readonly?].
record's primary key, and no callbacks are executed.
The row is simply removed with an SQL +DELETE+ statement on the
persisted). Returns the frozen instance.
reflect that no changes should be made (since they can't be
Deletes the record in the database and freezes this instance to
def delete _delete_row if persisted? @destroyed = true @previously_new_record = false freeze end
def destroy
and #destroy returns +false+.
before_destroy callback throws +:abort+ the action is cancelled
There's a series of callbacks associated with #destroy. If the
that no changes should be made (since they can't be persisted).
Deletes the record in the database and freezes this instance to reflect
def destroy _raise_readonly_record_error if readonly? destroy_associations @_trigger_destroy_callback = if persisted? destroy_row > 0 else true end @destroyed = true @previously_new_record = false freeze end
def destroy!
and #destroy! raises ActiveRecord::RecordNotDestroyed.
before_destroy callback throws +:abort+ the action is cancelled
There's a series of callbacks associated with #destroy!. If the
that no changes should be made (since they can't be persisted).
Deletes the record in the database and freezes this instance to reflect
def destroy! destroy || _raise_record_not_destroyed end
def destroy_associations
def destroy_associations end
def destroy_row
def destroy_row _delete_row end
def destroyed?
def destroyed? @destroyed end
def increment(attribute, by = 1)
The increment is performed directly on the underlying attribute, no setter is invoked.
Initializes +attribute+ to zero if +nil+ and adds the value passed as +by+ (default is 1).
def increment(attribute, by = 1) self[attribute] ||= 0 self[attribute] += by self end
def increment!(attribute, by = 1, touch: nil)
+update_counters+, see that for more.
Validations and callbacks are skipped. Supports the +touch+ option from
This means that any other modified attributes will still be dirty.
Only +attribute+ is updated; the record itself is not saved.
Wrapper around #increment that writes the update to the database.
def increment!(attribute, by = 1, touch: nil) 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
def new_record?
Returns true if this object hasn't been saved yet -- that is, a record
def new_record? @new_record end
def persisted?
Returns true if the record is persisted, i.e. it's not a new record and it was
def persisted? !(@new_record || @destroyed) end
def previously_new_record?
update or delete, the object didn't exist in the database and new_record? would have
Returns true if this object was just created -- that is, prior to the last
def previously_new_record? @previously_new_record end
def previously_persisted?
def previously_persisted? !new_record? && destroyed? end
def reload(options = nil)
end
end
end
retry
else
# If the record is gone there is nothing to do.
rescue ActiveRecord::RecordNotFound
reload
# Reload lock_version in particular.
begin
rescue ActiveRecord::StaleObjectError
yield
begin
def with_optimistic_retry
Another common use case is optimistic locking handling:
assert_equal 25, account.reload.credit # check it is also persisted
assert_equal 25, account.credit # check it is updated in memory
assert account.deposit!(25)
row in the database but not the object in memory:
written to the database, or when some action modifies the corresponding
Reloading is commonly used in test suites to test something is actually
reload(lock: true) # reload with pessimistic locking
The optional :lock flag option allows you to lock the reloaded record:
returns +self+ for convenience.
is raised. Otherwise, in addition to the in-place modification the method
If the record no longer exists in the database ActiveRecord::RecordNotFound
particular the associations cache and the QueryCache.
Attributes are reloaded from the database, and caches busted, in
# => #
# Account Load (1.2ms) SELECT "accounts".* FROM "accounts" WHERE "accounts"."id" = $1 LIMIT 1 [["id", 1]]
account.reload
account.id = 1
# => #
account = Account.new
manually) and modifies the receiver in-place:
This method finds the record by its primary key (which could be assigned
Reloads the record from the database.
def reload(options = nil) self.class.connection.clear_query_cache fresh_object = if apply_scoping?(options) _find_record(options) else self.class.unscoped { _find_record(options) } end @association_cache = fresh_object.instance_variable_get(:@association_cache) @attributes = fresh_object.instance_variable_get(:@attributes) @new_record = false @previously_new_record = false self end
def save(**options, &block)
Attributes marked as readonly are silently ignored if the record is
details.
#save returns +false+. See ActiveRecord::Callbacks for further
before_* callbacks throws +:abort+ the action is cancelled and
There's a series of callbacks associated with #save. If any of the
timestamps will not be updated.
the current time. However, if you supply touch: false, these
By default, #save also sets the +updated_at+/+updated_on+ attributes to
ActiveRecord::Validations for more information.
validate: false, validations are bypassed altogether. See
is cancelled and #save returns +false+, and the record won't be saved. However, if you supply
By default, save always runs validations. If any of them fail the action
the existing record gets updated.
If the model is new, a record gets created in the database, otherwise
Saves the model.
save(**options)
:call-seq:
#
def save(**options, &block) create_or_update(**options, &block) rescue ActiveRecord::RecordInvalid false end
def save!(**options, &block)
being updated.
Attributes marked as readonly are silently ignored if the record is
ActiveRecord::Callbacks for further details.
and #save! raises ActiveRecord::RecordNotSaved. See
the before_* callbacks throws +:abort+ the action is cancelled
There's a series of callbacks associated with #save!. If any of
timestamps will not be updated.
the current time. However, if you supply touch: false, these
By default, #save! also sets the +updated_at+/+updated_on+ attributes to
ActiveRecord::Validations for more information.
validate: false, validations are bypassed altogether. See
ActiveRecord::RecordInvalid gets raised, and the record won't be saved. However, if you supply
By default, #save! always runs validations. If any of them fail
the existing record gets updated.
If the model is new, a record gets created in the database, otherwise
Saves the model.
save!(**options)
:call-seq:
#
def save!(**options, &block) create_or_update(**options, &block) || raise(RecordNotSaved.new("Failed to save the record", self)) end
def strict_loaded_associations
def strict_loaded_associations @association_cache.find_all do |_, assoc| assoc.owner.strict_loading? && !assoc.owner.strict_loading_n_plus_one_only? end.map(&:first) end
def toggle(attribute)
user.banned? # => true
user.toggle(:banned)
user.banned? # => false
user = User.first
Example:
Returns +self+.
method toggles directly the underlying value without calling any setter.
if the predicate returns +true+ the attribute will become +false+. This
Assigns to +attribute+ the boolean opposite of attribute?. So
def toggle(attribute) self[attribute] = !public_send("#{attribute}?") self end
def toggle!(attribute)
Saving is not subjected to validation checks. Returns +true+ if the
its non-bang version in the sense that it passes through the attribute setter.
Wrapper around #toggle that saves the record. This method differs from
def toggle!(attribute) toggle(attribute).update_attribute(attribute, self[attribute]) end
def touch(*names, time: nil)
ball.touch(:updated_at) # => raises ActiveRecordError
ball = Ball.new
ActiveRecordError will be thrown. For example:
Note that +touch+ must be used on a persisted object, or else an
@brake.touch
# triggers @brake.car.touch and @brake.car.corporation.touch
end
belongs_to :corporation, touch: true
class Car < ActiveRecord::Base
end
belongs_to :car, touch: true
class Brake < ActiveRecord::Base
then +touch+ will invoke +touch+ method on associated object.
If used along with {belongs_to}[rdoc-ref:Associations::ClassMethods#belongs_to]
product.touch(:started_at, :ended_at) # updates started_at, ended_at and updated_at/on attributes
product.touch(:designed_at) # updates the designed_at attribute and updated_at/on
product.touch(time: Time.new(2015, 2, 16, 0, 0, 0)) # updates updated_at/on with specified time
product.touch # updates updated_at/on with current time
attributes. If no time argument is passed, the current time is used as default.
If attribute names are passed, they are updated along with updated_at/on
This method can be passed attribute names and an optional time argument.
+after_commit+ and +after_rollback+ callbacks are executed.
Please note that no validation is performed and only the +after_touch+,
or the time specified.
Saves the record with the updated_at/on attributes set to the current time
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 |= names.map! do |name| name = name.to_s self.class.attribute_aliases[name] || name end unless names.empty? unless attribute_names.empty? affected_rows = _touch_row(attribute_names, time) @_trigger_update_callback = affected_rows == 1 else true end end
def update(attributes)
record, all wrapped in a transaction. If the object is invalid, the saving
Updates the attributes of the model from the passed-in hash and saves the
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
def update!(attributes)
Updates its receiver just like #update but calls #save! instead
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
def update_attribute(name, value)
attribute is marked as readonly.
This method raises an ActiveRecord::ActiveRecordError if the
* Updates all the attributes that are dirty in this object.
* updated_at/updated_on column is updated if that column is available.
* \Callbacks are invoked.
* Validation is skipped.
This is especially useful for boolean flags on existing records. Also note that
Updates a single attribute and saves the record.
def update_attribute(name, value) name = name.to_s verify_readonly_attribute(name) public_send("#{name}=", value) save(validate: false) end
def update_column(name, value)
update_columns(name => value)
.
def update_column(name, value) update_columns(name => value) end
def update_columns(attributes)
This method raises an ActiveRecord::ActiveRecordError when called on new
* However, attributes are serialized with the same rules as ActiveRecord::Relation#update_all
* +updated_at+/+updated_on+ are not updated.
* \Callbacks are skipped.
* \Validations are skipped.
procedures are totally bypassed. In particular:
the database, but take into account that in consequence the regular update
This is the fastest way to update attributes because it goes straight to
user.update_columns(last_request_at: Time.current)
statement and sets them in the receiver:
Updates the attributes directly in the database issuing an UPDATE SQL
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 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
def verify_readonly_attribute(name)
def verify_readonly_attribute(name) raise ActiveRecordError, "#{name} is marked as readonly" if self.class.readonly_attribute?(name) end