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

new_record[RW]
new_record?[RW]

Public Instance Methods

decrement(attribute, by = 1) click to toggle source

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
decrement!(attribute, by = 1, touch: nil) click to toggle source

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() click to toggle source

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
destroy() click to toggle source

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
destroy!() click to toggle source

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
increment(attribute, by = 1) click to toggle source

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
increment!(attribute, by = 1, touch: nil) click to toggle source

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
persisted?() click to toggle source

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
save(options = {}) click to toggle source

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
touch(*names, time: nil) click to toggle source

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(conditions = {}, &block) click to toggle source

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!(conditions = {}) { |t| ... } click to toggle source

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_attribute(attribute, value) click to toggle source

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_attributes(attributes) click to toggle source

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_attributes!(attributes) click to toggle source

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