Module: Dynamoid::Persistence
- Extended by:
- ActiveSupport::Concern
- Included in:
- Components
- Defined in:
- lib/dynamoid/persistence.rb,
lib/dynamoid/persistence/inc.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,
lib/dynamoid/persistence/item_updater_with_dumping.rb,
lib/dynamoid/persistence/item_updater_with_casting_and_dumping.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, Inc, ItemUpdaterWithCastingAndDumping, ItemUpdaterWithDumping, Save, UpdateFields, Upsert
Constant Summary collapse
- UNIX_EPOCH_DATE =
Date.new(1970, 1, 1).freeze
Instance Attribute Summary collapse
-
#destroyed ⇒ Object
(also: #destroyed?)
Returns the value of attribute destroyed.
-
#new_record ⇒ Object
(also: #new_record?)
Returns the value of attribute new_record.
Instance Method Summary collapse
-
#decrement(attribute, by = 1) ⇒ Dynamoid::Document
Change numeric attribute value.
-
#decrement!(attribute, by = 1, touch: nil) ⇒ Dynamoid::Document
Change numeric attribute value and save a model.
-
#delete ⇒ Dynamoid::Document
Delete a model.
-
#destroy ⇒ Dynamoid::Document|false
Delete a model.
-
#destroy! ⇒ Object
Delete a model.
-
#increment(attribute, by = 1) ⇒ Dynamoid::Document
Change numeric attribute value.
-
#increment!(attribute, by = 1, touch: nil) ⇒ Dynamoid::Document
Change numeric attribute value and save a model.
-
#persisted? ⇒ true|false
Is this object persisted in DynamoDB?.
-
#save(options = {}) ⇒ true|false
Create new model or persist changes.
-
#save!(options = {}) ⇒ true|false
Create new model or persist changes.
-
#touch(*names, time: nil) ⇒ Dynamoid::Document
Update document timestamps.
-
#update(conditions = {}, &block) ⇒ true|false
Update a model.
-
#update!(conditions = {}) ⇒ Dynamoid::Document
Update a model.
-
#update_attribute(attribute, value) ⇒ Dynamoid::Document
Update a single attribute, saving the object afterwards.
-
#update_attributes(attributes) ⇒ true|false
Update multiple attributes at once, saving the object once the updates are complete.
-
#update_attributes!(attributes) ⇒ Object
Update multiple attributes at once, saving the object once the updates are complete.
Instance Attribute Details
#destroyed ⇒ Object Also known as: destroyed?
Returns the value of attribute destroyed.
22 23 24 |
# File 'lib/dynamoid/persistence.rb', line 22 def destroyed @destroyed end |
#new_record ⇒ Object Also known as: new_record?
Returns the value of attribute new_record.
22 23 24 |
# File 'lib/dynamoid/persistence.rb', line 22 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)
972 973 974 |
# File 'lib/dynamoid/persistence.rb', line 972 def decrement(attribute, by = 1) increment(attribute, -by) end |
#decrement!(attribute, by = 1, touch: nil) ⇒ Dynamoid::Document
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])
1000 1001 1002 |
# File 'lib/dynamoid/persistence.rb', line 1000 def decrement!(attribute, by = 1, touch: nil) increment!(attribute, -by, touch: touch) end |
#delete ⇒ Dynamoid::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.
Raises Dynamoid::Errors::MissingHashKey
if a partition key has value nil
and raises Dynamoid::Errors::MissingRangeKey
if a sort key is required but has value nil
.
1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 |
# File 'lib/dynamoid/persistence.rb', line 1060 def delete raise Dynamoid::Errors::MissingHashKey if hash_key.nil? raise Dynamoid::Errors::MissingRangeKey if self.class.range_key? && range_value.nil? = range_key ? { range_key: Dumping.dump_field(read_attribute(range_key), self.class.attributes[range_key]) } : {} partition_key_dumped = Dumping.dump_field(hash_key, self.class.attributes[self.class.hash_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 [:conditions] = conditions end @destroyed = true Dynamoid.adapter.delete(self.class.table_name, partition_key_dumped, ) self.class.associations.each_key do |name| send(name).disassociate_source end self rescue Dynamoid::Errors::ConditionalCheckFailedException raise Dynamoid::Errors::StaleObjectError.new(self, 'delete') end |
#destroy ⇒ Dynamoid::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.
Raises Dynamoid::Errors::MissingHashKey
if a partition key has value nil
and raises Dynamoid::Errors::MissingRangeKey
if a sort key is required but has value nil
.
1019 1020 1021 1022 1023 1024 1025 1026 1027 |
# File 'lib/dynamoid/persistence.rb', line 1019 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.
Raises Dynamoid::Errors::MissingHashKey
if a partition key has value nil
and raises Dynamoid::Errors::MissingRangeKey
if a sort key is required but has value nil
.
1042 1043 1044 |
# File 'lib/dynamoid/persistence.rb', line 1042 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)
919 920 921 922 923 |
# File 'lib/dynamoid/persistence.rb', line 919 def increment(attribute, by = 1) self[attribute] ||= 0 self[attribute] += by self end |
#increment!(attribute, by = 1, touch: nil) ⇒ Dynamoid::Document
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])
949 950 951 952 953 954 955 956 957 958 959 |
# File 'lib/dynamoid/persistence.rb', line 949 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? ⇒ true|false
Is this object persisted in DynamoDB?
user = User.new
user.persisted? # => false
user.save
user.persisted? # => true
509 510 511 |
# File 'lib/dynamoid/persistence.rb', line 509 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 updates 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.
Raises Dynamoid::Errors::MissingHashKey
if a model is already persisted and a partition key has value nil
and raises Dynamoid::Errors::MissingRangeKey
if a sort key is required but has value nil
.
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.
567 568 569 570 571 572 573 574 575 576 577 578 579 |
# File 'lib/dynamoid/persistence.rb', line 567 def save( = {}) if Dynamoid.config.create_table_on_save self.class.create_table(sync: true) end create_or_update = new_record? ? :create : :update run_callbacks(:save) do run_callbacks(create_or_update) do Save.call(self, touch: [:touch]) end end end |
#save!(options = {}) ⇒ true|false
Create new model or persist changes.
Run the validation and callbacks. Raises Dynamoid::Errors::DocumentNotValid
is validation fails.
user = User.create
user.age = 26
user.save! # => user
Validation can be skipped with validate: false option:
user = User.new(age: -1)
user.save!(validate: false) # => user
save!
by default sets timestamps attributes - created_at
and updated_at
when creates new model and updates updated_at
attribute when updates 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.
save!
method call raises Dynamoid::Errors::RecordNotSaved
exception if some callback aborted execution.
Raises Dynamoid::Errors::MissingHashKey
if a model is already persisted and a partition key has value nil
and raises Dynamoid::Errors::MissingRangeKey
if a sort key is required but has value nil
.
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.
636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 |
# File 'lib/dynamoid/persistence.rb', line 636 def save!( = {}) # validation is handled in the Validation module if Dynamoid.config.create_table_on_save self.class.create_table(sync: true) end create_or_update = new_record? ? :create : :update aborted = true run_callbacks(:save) do run_callbacks(create_or_update) do aborted = false Save.call(self, touch: [:touch]) end end if aborted raise Dynamoid::Errors::RecordNotSaved, self end self end |
#touch(*names, time: nil) ⇒ Dynamoid::Document
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.
476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 |
# File 'lib/dynamoid/persistence.rb', line 476 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) ⇒ true|false
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 set.
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
To check if some attribute (or attributes) isn’t stored in a DynamoDB item (e.g. it wasn’t set explicitly) there is another condition - unless_exists
:
user = User.create(name: 'Tylor')
user.update(unless_exists: [:age]) do |t|
t.set(age: 18)
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.
901 902 903 904 905 906 |
# File 'lib/dynamoid/persistence.rb', line 901 def update(conditions = {}, &block) update!(conditions, &block) true rescue Dynamoid::Errors::StaleObjectError false end |
#update!(conditions = {}) ⇒ Dynamoid::Document
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 set.
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 atomic operations. 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
To check if some attribute (or attributes) isn’t stored in a DynamoDB item (e.g. it wasn’t set explicitly) there is another condition - unless_exists
:
user = User.create(name: 'Tylor')
user.update!(unless_exists: [:age]) do |t|
t.set(age: 18)
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.
786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 |
# File 'lib/dynamoid/persistence.rb', line 786 def update!(conditions = {}) run_callbacks(:update) do = {} if range_key value = read_attribute(range_key) = self.class.attributes[range_key] [:range_key] = Dumping.dump_field(value, ) end begin table_name = self.class.table_name partition_key_dumped = Dumping.dump_field(hash_key, self.class.attributes[self.class.hash_key]) conditions = conditions.dup conditions[:if] ||= {} conditions[:if][self.class.hash_key] = partition_key_dumped if self.class.range_key sort_key_dumped = Dumping.dump_field(range_value, self.class.attributes[self.class.range_key]) conditions[:if][self.class.range_key] = sort_key_dumped end = .merge(conditions: conditions) new_attrs = Dynamoid.adapter.update_item(table_name, partition_key_dumped, ) do |t| item_updater = ItemUpdaterWithDumping.new(self.class, t) item_updater.add(lock_version: 1) if self.class.attributes[:lock_version] if self.class. item_updater.set(updated_at: DateTime.now.in_time_zone(Time.zone)) end yield t end load(Undumping.undump_attributes(new_attrs, self.class.attributes)) rescue Dynamoid::Errors::ConditionalCheckFailedException # exception may be raised either because of failed user provided conditions # or because of conditions on partition and sort keys. We cannot # distinguish these two cases. 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')
Validation is skipped.
Raises a Dynamoid::Errors::UnknownAttribute
exception if any of the attributes is not on the model
720 721 722 |
# File 'lib/dynamoid/persistence.rb', line 720 def update_attribute(attribute, value) # final implementation is in the Dynamoid::Validation module 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
Raises Dynamoid::Errors::MissingHashKey
if a partition key has value nil
and raises Dynamoid::Errors::MissingRangeKey
if a sort key is required but has value nil
.
678 679 680 681 |
# File 'lib/dynamoid/persistence.rb', line 678 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
Raises Dynamoid::Errors::MissingHashKey
if a partition key has value nil
and raises Dynamoid::Errors::MissingRangeKey
if a sort key is required but has value nil
.
699 700 701 702 |
# File 'lib/dynamoid/persistence.rb', line 699 def update_attributes!(attributes) attributes.each { |attribute, value| write_attribute(attribute, value) } save! end |