Module: Dynamoid::Persistence

Extended by:
ActiveSupport::Concern
Included in:
Components
Defined in:
lib/dynamoid/persistence.rb,
lib/dynamoid/persistence/save.rb,
lib/dynamoid/persistence/import.rb,
lib/dynamoid/persistence/upsert.rb,
lib/dynamoid/persistence/update_fields.rb,
lib/dynamoid/persistence/update_validations.rb

Overview

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.

Defined Under Namespace

Modules: ClassMethods, UpdateValidations Classes: Import, Save, UpdateFields, Upsert

Constant Summary collapse

UNIX_EPOCH_DATE =
Date.new(1970, 1, 1).freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#new_recordObject Also known as: new_record?

Returns the value of attribute new_record.



20
21
22
# File 'lib/dynamoid/persistence.rb', line 20

def new_record
  @new_record
end

Instance Method Details

#decrement(attribute, by = 1) ⇒ Dynamoid::Document

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)

Parameters:

  • attribute (Symbol)

    attribute name

  • by (Numeric) (defaults to: 1)

    value to subtract (optional)

Returns:



760
761
762
763
764
# File 'lib/dynamoid/persistence.rb', line 760

def decrement(attribute, by = 1)
  self[attribute] ||= 0
  self[attribute] -= by
  self
end

#decrement!(attribute, by = 1) ⇒ true|false

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)

Returns true if a model was saved and false otherwise.

Parameters:

  • attribute (Symbol)

    attribute name

  • by (Numeric) (defaults to: 1)

    value to subtract (optional)

Returns:

  • (true|false)

    whether saved model successfully



779
780
781
782
# File 'lib/dynamoid/persistence.rb', line 779

def decrement!(attribute, by = 1)
  decrement(attribute, by)
  save
end

#deleteDynamoid::Document

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.

Returns:

Since:

  • 0.2.0



828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
# File 'lib/dynamoid/persistence.rb', line 828

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

#destroyDynamoid::Document|false

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.

Returns:

Since:

  • 0.2.0



795
796
797
798
799
800
801
802
803
# File 'lib/dynamoid/persistence.rb', line 795

def destroy
  ret = run_callbacks(:destroy) do
    delete
  end

  @destroyed = true

  ret == false ? false : self
end

#destroy!Object

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.



814
815
816
# File 'lib/dynamoid/persistence.rb', line 814

def destroy!
  destroy || (raise Dynamoid::Errors::RecordNotDestroyed, self)
end

#increment(attribute, by = 1) ⇒ Dynamoid::Document

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)

Parameters:

  • attribute (Symbol)

    attribute name

  • by (Numeric) (defaults to: 1)

    value to add (optional)

Returns:



725
726
727
728
729
# File 'lib/dynamoid/persistence.rb', line 725

def increment(attribute, by = 1)
  self[attribute] ||= 0
  self[attribute] += by
  self
end

#increment!(attribute, by = 1) ⇒ true|false

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)

Returns true if a model was saved and false otherwise.

Parameters:

  • attribute (Symbol)

    attribute name

  • by (Numeric) (defaults to: 1)

    value to add (optional)

Returns:

  • (true|false)

    whether saved model successfully



744
745
746
747
# File 'lib/dynamoid/persistence.rb', line 744

def increment!(attribute, by = 1)
  increment(attribute, by)
  save
end

#persisted?true|false

Is this object persisted in DynamoDB?

user = User.new
user.persisted? # => false

user.save
user.persisted? # => true

Returns:

  • (true|false)

Since:

  • 0.2.0



437
438
439
# File 'lib/dynamoid/persistence.rb', line 437

def persisted?
  !(new_record? || @destroyed)
end

#save(options = {}) ⇒ true|false

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.

Parameters:

  • options (Hash) (defaults to: {})

    (optional)

Options Hash (options):

  • :validate (true|false)

    validate a model or not - true by default (optional)

  • :touch (true|false)

    update tiemstamps fields or not - true by default (optional)

Returns:

  • (true|false)

    Whether saving successful or not

Since:

  • 0.2.0



489
490
491
492
493
494
495
496
497
498
499
500
501
# File 'lib/dynamoid/persistence.rb', line 489

def save(options = {})
  self.class.create_table(sync: true)

  @_touch_record = options[:touch]

  create_or_update = new_record? ? :create : :update

  run_callbacks(create_or_update) do
    run_callbacks(:save) do
      Save.call(self)
    end
  end
end

#touch(name = nil) ⇒ Dynamoid::Document

Update document timestamps.

Set updated_at attribute to current DateTime.

post.touch

Can update another field in addition with the same timestamp if it’s name passed as argument.

user.touch(:last_login_at)

Parameters:

  • name (Symbol) (defaults to: nil)

    attribute name to update (optional)

Returns:



419
420
421
422
423
424
425
# File 'lib/dynamoid/persistence.rb', line 419

def touch(name = nil)
  now = DateTime.now
  self.updated_at = now
  attributes[name] = now if name
  save
  self
end

#update(conditions = {}, &block) ⇒ true|false

Update a model.

Runs validation and 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.

Parameters:

  • conditions (Hash) (defaults to: {})

    Conditions on model attributes to make a conditional update (optional)

Returns:

  • (true|false)
    • whether conditions are met and updating is successful



707
708
709
710
711
712
# File 'lib/dynamoid/persistence.rb', line 707

def update(conditions = {}, &block)
  update!(conditions, &block)
  true
rescue Dynamoid::Errors::StaleObjectError
  false
end

#update!(conditions = {}) ⇒ Dynamoid::Document

Update a model.

Runs validation and 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.

Parameters:

  • conditions (Hash) (defaults to: {})

    Conditions on model attributes to make a conditional update (optional)

Returns:



612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
# File 'lib/dynamoid/persistence.rb', line 612

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) ⇒ Dynamoid::Document

Update a single attribute, saving the object afterwards.

Returns true if saving is successful and false otherwise.

user.update_attribute(:last_name, 'Tylor')

Raises a Dynamoid::Errors::UnknownAttribute exception if any of the attributes is not on the model

Parameters:

  • attribute (Symbol)

    attribute name to update

  • value (Object)

    the value to assign it

Returns:

Since:

  • 0.2.0



551
552
553
554
555
556
# File 'lib/dynamoid/persistence.rb', line 551

def update_attribute(attribute, value)
  # final implementation is in the Dynamoid::Validation module
  write_attribute(attribute, value)
  save
  self
end

#update_attributes(attributes) ⇒ true|false

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

Parameters:

  • attributes (Hash)

    a hash of attributes to update

Returns:

  • (true|false)

    Whether updating successful or not

Since:

  • 0.2.0



515
516
517
518
# File 'lib/dynamoid/persistence.rb', line 515

def update_attributes(attributes)
  attributes.each { |attribute, value| write_attribute(attribute, value) }
  save
end

#update_attributes!(attributes) ⇒ Object

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

Parameters:

  • attributes (Hash)

    a hash of attributes to update



532
533
534
535
# File 'lib/dynamoid/persistence.rb', line 532

def update_attributes!(attributes)
  attributes.each { |attribute, value| write_attribute(attribute, value) }
  save!
end