module Dynamoid::Persistence
Persistence
is responsible for dumping objects to and marshalling objects from the data store. It tries to reserialize values to be of the same type as when they were passed in, based on the fields in the class.
Constants
- UNIX_EPOCH_DATE
@private
Attributes
Public Instance Methods
Change numeric attribute value.
Initializes attribute to zero if nil
and subtracts the specified value (by default is 1). Only makes sense for number-based attributes.
user.decrement(:followers_count) user.decrement(:followers_count, 2)
@param attribute [Symbol] attribute name @param by [Numeric] value to subtract (optional) @return [Dynamoid::Document] self
# File lib/dynamoid/persistence.rb, line 797 def decrement(attribute, by = 1) increment(attribute, -by) end
Change numeric attribute value and save a model.
Initializes attribute to zero if nil
and subtracts the specified value (by default is 1). Only makes sense for number-based attributes.
user.decrement!(:followers_count) user.decrement!(:followers_count, 2)
Only ‘attribute` is saved. The model itself is not saved. So any other modified attributes will still be dirty. Validations
and callbacks are skipped.
When ‘:touch` option is passed the timestamp columns are updating. If attribute names are passed, they are updated along with updated_at attribute:
user.decrement!(:followers_count, touch: true) user.decrement!(:followers_count, touch: :viewed_at) user.decrement!(:followers_count, touch: [:viewed_at, :accessed_at])
@param attribute [Symbol] attribute name @param by [Numeric] value to subtract (optional) @param touch [true | Symbol | Array] to update update_at attribute and optionally the specified ones @return [Dynamoid::Document] self
# File lib/dynamoid/persistence.rb, line 825 def decrement!(attribute, by = 1, touch: nil) increment!(attribute, -by, touch: touch) end
Delete a model.
Supports optimistic locking with the lock_version
attribute and doesn’t delete a model if it’s already changed.
Raises Dynamoid::Errors::StaleObjectError
exception if cannot delete a model.
@return [Dynamoid::Document] self @since 0.2.0
# File lib/dynamoid/persistence.rb, line 873 def delete options = range_key ? { range_key: Dumping.dump_field(read_attribute(range_key), self.class.attributes[range_key]) } : {} # Add an optimistic locking check if the lock_version column exists if self.class.attributes[:lock_version] conditions = { if: {} } conditions[:if][:lock_version] = if changes[:lock_version].nil? lock_version else changes[:lock_version][0] end options[:conditions] = conditions end @destroyed = true Dynamoid.adapter.delete(self.class.table_name, hash_key, options) self.class.associations.each do |name, _options| send(name).disassociate_source end self rescue Dynamoid::Errors::ConditionalCheckFailedException raise Dynamoid::Errors::StaleObjectError.new(self, 'delete') end
Delete a model.
Runs callbacks.
Supports optimistic locking with the lock_version
attribute and doesn’t delete a model if it’s already changed.
Returns self
if deleted successfully and false
otherwise.
@return [Dynamoid::Document|false] whether deleted successfully @since 0.2.0
# File lib/dynamoid/persistence.rb, line 840 def destroy ret = run_callbacks(:destroy) do delete end @destroyed = true ret == false ? false : self end
Delete a model.
Runs callbacks.
Supports optimistic locking with the lock_version
attribute and doesn’t delete a model if it’s already changed.
Raises Dynamoid::Errors::RecordNotDestroyed
exception if model deleting failed.
# File lib/dynamoid/persistence.rb, line 859 def destroy! destroy || (raise Dynamoid::Errors::RecordNotDestroyed, self) end
Change numeric attribute value.
Initializes attribute to zero if nil
and adds the specified value (by default is 1). Only makes sense for number-based attributes.
user.increment(:followers_count) user.increment(:followers_count, 2)
@param attribute [Symbol] attribute name @param by [Numeric] value to add (optional) @return [Dynamoid::Document] self
# File lib/dynamoid/persistence.rb, line 744 def increment(attribute, by = 1) self[attribute] ||= 0 self[attribute] += by self end
Change numeric attribute value and save a model.
Initializes attribute to zero if nil
and adds the specified value (by default is 1). Only makes sense for number-based attributes.
user.increment!(:followers_count) user.increment!(:followers_count, 2)
Only ‘attribute` is saved. The model itself is not saved. So any other modified attributes will still be dirty. Validations
and callbacks are skipped.
When ‘:touch` option is passed the timestamp columns are updating. If attribute names are passed, they are updated along with updated_at attribute:
user.increment!(:followers_count, touch: true) user.increment!(:followers_count, touch: :viewed_at) user.increment!(:followers_count, touch: [:viewed_at, :accessed_at])
@param attribute [Symbol] attribute name @param by [Numeric] value to add (optional) @param touch [true | Symbol | Array] to update update_at attribute and optionally the specified ones @return [Dynamoid::Document] self
# File lib/dynamoid/persistence.rb, line 774 def increment!(attribute, by = 1, touch: nil) increment(attribute, by) change = read_attribute(attribute) - (attribute_was(attribute) || 0) run_callbacks :touch do self.class.inc(hash_key, range_value, attribute => change, touch: touch) clear_attribute_changes(attribute) end self end
Is this object persisted in DynamoDB?
user = User.new user.persisted? # => false user.save user.persisted? # => true
@return [true|false] @since 0.2.0
# File lib/dynamoid/persistence.rb, line 457 def persisted? !(new_record? || @destroyed) end
Create new model or persist changes.
Run the validation and callbacks. Returns true
if saving is successful and false
otherwise.
user = User.new user.save # => true user.age = 26 user.save # => true
Validation can be skipped with +validate: false+ option:
user = User.new(age: -1) user.save(validate: false) # => true
save
by default sets timestamps attributes - created_at
and updated_at
when creates new model and updates updated_at
attribute when update already existing one.
Changing updated_at
attribute at updating a model can be skipped with +touch: false+ option:
user.save(touch: false)
If a model is new and hash key (id
by default) is not assigned yet it was assigned implicitly with random UUID value.
If lock_version
attribute is declared it will be incremented. If it’s blank then it will be initialized with 1.
save
method call raises Dynamoid::Errors::RecordNotUnique
exception if primary key (hash key + optional range key) already exists in a table.
save
method call raises Dynamoid::Errors::StaleObjectError
exception if there is lock_version
attribute and the document in a table was already changed concurrently and lock_version
was consequently increased.
When a table is not created yet the first save
method call will create a table. It’s useful in test environment to avoid explicit table creation.
@param options [Hash] (optional) @option options [true|false] :validate validate a model or not - true
by default (optional) @option options [true|false] :touch update tiemstamps fields or not - true
by default (optional) @return [true|false] Whether saving successful or not @since 0.2.0
# File lib/dynamoid/persistence.rb, line 509 def save(options = {}) self.class.create_table(sync: true) create_or_update = new_record? ? :create : :update run_callbacks(:save) do run_callbacks(create_or_update) do Save.call(self, touch: options[:touch]) end end end
Update document timestamps.
Set updated_at
attribute to current DateTime.
post.touch
Can update other fields in addition with the same timestamp if their names passed as arguments.
user.touch(:last_login_at, :viewed_at)
Some specific value can be used to save:
user.touch(time: 1.hour.ago)
No validation is performed and only after_touch
callback is called.
The method must be used on a persisted object, otherwise Dynamoid::Errors::Error
will be thrown.
@param names [*Symbol] a list of attribute names to update (optional) @param time [Time] datetime value that can be used instead of the current time (optional) @return [Dynamoid::Document] self
# File lib/dynamoid/persistence.rb, line 424 def touch(*names, time: nil) if new_record? raise Dynamoid::Errors::Error, 'cannot touch on a new or destroyed record object' end time_to_assign = time || DateTime.now self.updated_at = time_to_assign names.each do |name| attributes[name] = time_to_assign end attribute_names = names.map(&:to_sym) + [:updated_at] attributes_with_values = attributes.slice(*attribute_names) run_callbacks :touch do self.class.update_fields(hash_key, range_value, attributes_with_values) clear_attribute_changes(attribute_names.map(&:to_s)) end self end
Update a model.
Doesn’t run validation. Runs only update
callbacks. Reloads all attribute values.
Accepts mandatory block in order to specify operations which will modify attributes. Supports following operations: add
, delete
and set
.
Operation add
just adds a value for numeric attributes and join collections if attribute is a collection (one of array
, set
or map
).
user.update do |t| t.add(age: 1, followers_count: 5) t.add(hobbies: ['skying', 'climbing']) end
Operation delete
is applied to collection attribute types and substructs one collection from another.
user.update do |t| t.delete(hobbies: ['skying']) end
If it’s applied to a scalar attribute then the item’s attribute is removed at all:
user.update do |t| t.delete(age: nil) end
or even without useless value at all:
user.update do |t| t.delete(:age) end
Operation set
just changes an attribute value:
user.update do |t| t.set(age: 21) end
All the operations works like ADD
, DELETE
and PUT
actions supported by AttributeUpdates
parameter of UpdateItem
operation.
Can update a model conditionaly:
user.update(if: { age: 20 }) do |t| t.add(age: 1) end
If a document doesn’t meet conditions it just returns false
. Otherwise it returns true
.
It will increment the lock_version
attribute if a table has the column, but will not check it. Thus, a concurrent save
call will never cause an update!
to fail, but an update!
may cause a concurrent save
to fail.
@param conditions [Hash] Conditions on model attributes to make a conditional update (optional) @return [true|false] - whether conditions are met and updating is successful
# File lib/dynamoid/persistence.rb, line 726 def update(conditions = {}, &block) update!(conditions, &block) true rescue Dynamoid::Errors::StaleObjectError false end
Update a model.
Doesn’t run validation. Runs only update
callbacks. Reloads all attribute values.
Accepts mandatory block in order to specify operations which will modify attributes. Supports following operations: add
, delete
and set
.
Operation add
just adds a value for numeric attributes and join collections if attribute is a collection (one of array
, set
or map
).
user.update! do |t| t.add(age: 1, followers_count: 5) t.add(hobbies: ['skying', 'climbing']) end
Operation delete
is applied to collection attribute types and substructs one collection from another.
user.update! do |t| t.delete(hobbies: ['skying']) end
Operation set
just changes an attribute value:
user.update! do |t| t.set(age: 21) end
All the operations work like ADD
, DELETE
and PUT
actions supported by AttributeUpdates
parameter of UpdateItem
operation.
It’s an atomic operation. So adding or deleting elements in a collection or incrementing or decrementing a numeric field is atomic and does not interfere with other write requests.
Can update a model conditionaly:
user.update!(if: { age: 20 }) do |t| t.add(age: 1) end
If a document doesn’t meet conditions it raises Dynamoid::Errors::StaleObjectError
exception.
It will increment the lock_version
attribute if a table has the column, but will not check it. Thus, a concurrent save
call will never cause an update!
to fail, but an update!
may cause a concurrent save
to fail.
@param conditions [Hash] Conditions on model attributes to make a conditional update (optional) @return [Dynamoid::Document] self
# File lib/dynamoid/persistence.rb, line 631 def update!(conditions = {}) run_callbacks(:update) do options = {} if range_key value = read_attribute(range_key) attribute_options = self.class.attributes[range_key] options[:range_key] = Dumping.dump_field(value, attribute_options) end begin table_name = self.class.table_name update_item_options = options.merge(conditions: conditions) new_attrs = Dynamoid.adapter.update_item(table_name, hash_key, update_item_options) do |t| t.add(lock_version: 1) if self.class.attributes[:lock_version] if self.class.timestamps_enabled? time_now = DateTime.now.in_time_zone(Time.zone) time_now_dumped = Dumping.dump_field(time_now, self.class.attributes[:updated_at]) t.set(updated_at: time_now_dumped) end yield t end load(Undumping.undump_attributes(new_attrs, self.class.attributes)) rescue Dynamoid::Errors::ConditionalCheckFailedException raise Dynamoid::Errors::StaleObjectError.new(self, 'update') end end self end
Update a single attribute, saving the object afterwards.
Returns true
if saving is successful and false
otherwise.
user.update_attribute(:last_name, 'Tylor')
Validation is skipped.
Raises a Dynamoid::Errors::UnknownAttribute
exception if any of the attributes is not on the model
@param attribute [Symbol] attribute name to update @param value [Object] the value to assign it @return [Dynamoid::Document] self
@since 0.2.0
# File lib/dynamoid/persistence.rb, line 570 def update_attribute(attribute, value) # final implementation is in the Dynamoid::Validation module write_attribute(attribute, value) save self end
Update multiple attributes at once, saving the object once the updates are complete. Returns true
if saving is successful and false
otherwise.
user.update_attributes(age: 27, last_name: 'Tylor')
Raises a Dynamoid::Errors::UnknownAttribute
exception if any of the attributes is not on the model
@param attributes [Hash] a hash of attributes to update @return [true|false] Whether updating successful or not @since 0.2.0
# File lib/dynamoid/persistence.rb, line 532 def update_attributes(attributes) attributes.each { |attribute, value| write_attribute(attribute, value) } save end
Update multiple attributes at once, saving the object once the updates are complete.
user.update_attributes!(age: 27, last_name: 'Tylor')
Raises a Dynamoid::Errors::DocumentNotValid
exception if some vaidation fails.
Raises a Dynamoid::Errors::UnknownAttribute
exception if any of the attributes is not on the model
@param attributes [Hash] a hash of attributes to update
# File lib/dynamoid/persistence.rb, line 549 def update_attributes!(attributes) attributes.each { |attribute, value| write_attribute(attribute, value) } save! end