Class: Dynamoid::TransactionWrite
- Inherits:
-
Object
- Object
- Dynamoid::TransactionWrite
- Defined in:
- lib/dynamoid/transaction_write.rb,
lib/dynamoid/transaction_write/base.rb,
lib/dynamoid/transaction_write/save.rb,
lib/dynamoid/transaction_write/create.rb,
lib/dynamoid/transaction_write/upsert.rb,
lib/dynamoid/transaction_write/destroy.rb,
lib/dynamoid/transaction_write/update_fields.rb,
lib/dynamoid/transaction_write/update_attributes.rb,
lib/dynamoid/transaction_write/delete_with_instance.rb,
lib/dynamoid/transaction_write/delete_with_primary_key.rb
Overview
The class TransactionWrite
provides means to perform multiple modifying operations in transaction, that is atomically, so that either all of them succeed, or all of them fail.
The persisting methods are supposed to be as close as possible to their non-transactional counterparts like .create
, #save
and #delete
:
user = User.new()
payment = Payment.find(1)
Dynamoid::TransactionWrite.execute do |t|
t.save! user
t.create! Account, name: 'A'
t.delete payment
end
The only difference is that the methods are called on a transaction instance and a model or a model class should be specified.
So user.save!
becomes t.save!(user), Account.create!(name: ‘A’) becomes t.create!(Account, name: ‘A’), and payment.delete
becomes t.delete(payment).
A transaction can be used without a block. This way a transaction instance should be instantiated and committed manually with #commit
method:
t = Dynamoid::TransactionWrite.new
t.save! user
t.create! Account, name: 'A'
t.delete payment
t.commit
Some persisting methods are intentionally not available in a transaction, e.g. .update
and .update!
that simply call .find
and #update_attributes
methods. These methods perform multiple operations so cannot be implemented in a transactional atomic way.
### DynamoDB’s transactions
The main difference between DynamoDB transactions and a common interface is that DynamoDB’s transactions are executed in batch. So in Dynamoid no changes are actually persisted when some transactional method (e.g+ ‘#save+) is called. All the changes are persisted at the end.
A TransactWriteItems
DynamoDB operation is used (see [documentation](docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_TransactWriteItems.html) for details).
### Callbacks
The transactional methods support before_
, after_
and around_
callbacks to the extend the non-transactional methods support them.
There is important difference - a transactional method runs callbacks immediately (even after_
ones) when it is called before changes are actually persisted. So code in after_
callbacks does not see observes them in DynamoDB and so for.
When a callback aborts persisting of a model or a model is invalid then transaction is not aborted and may commit successfully.
### Transaction rollback
A transaction is rolled back on DynamoDB’s side automatically when:
-
an ongoing operation is in the process of updating the same item.
-
there is insufficient provisioned capacity for the transaction to be completed.
-
an item size becomes too large (bigger than 400 KB), a local secondary index (LSI) becomes too large, or a similar validation error occurs because of changes made by the transaction.
-
the aggregate size of the items in the transaction exceeds 4 MB.
-
there is a user error, such as an invalid data format.
A transaction can be interrupted simply by an exception raised within a block. As far as no changes are actually persisted before the #commit
method call - there is nothing to undo on the DynamoDB’s site.
Raising Dynamoid::Errors::Rollback
exception leads to interrupting a transation and it isn’t propogated:
Dynamoid::TransactionWrite.execute do |t|
t.save! user
t.create! Account, name: 'A'
if user.is_admin?
raise Dynamoid::Errors::Rollback
end
end
When a transaction is successfully committed or rolled backed - corresponding #after_commit
or #after_rollback
callbacks are run for each involved model.
Defined Under Namespace
Classes: Base, Create, DeleteWithInstance, DeleteWithPrimaryKey, Destroy, Save, UpdateAttributes, UpdateFields, Upsert
Class Method Summary collapse
Instance Method Summary collapse
-
#commit ⇒ Object
Persist all the changes.
-
#create(model_class, attributes = {}, &block) ⇒ Dynamoid::Document
Create a model.
-
#create!(model_class, attributes = {}, &block) ⇒ Dynamoid::Document
Create a model.
-
#delete(model_or_model_class, hash_key = nil, range_key = nil) ⇒ Dynamoid::Document
Delete a model.
-
#destroy(model) ⇒ Dynamoid::Document
Delete a model.
-
#destroy!(model) ⇒ Dynamoid::Document|false
Delete a model.
-
#initialize ⇒ TransactionWrite
constructor
A new instance of TransactionWrite.
- #rollback ⇒ Object
-
#save(model, **options) ⇒ true|false
Create new model or persist changes in already existing one.
-
#save!(model, **options) ⇒ true|false
Create new model or persist changes in already existing one.
-
#update_attributes(model, attributes) ⇒ true|false
Update multiple attributes at once.
-
#update_attributes!(model, attributes) ⇒ Object
Update multiple attributes at once.
-
#update_fields(model_class, hash_key, range_key = nil, attributes) ⇒ nil
Update document.
-
#upsert(model_class, hash_key, range_key = nil, attributes) ⇒ nil
Update an existing document or create a new one.
Constructor Details
#initialize ⇒ TransactionWrite
Returns a new instance of TransactionWrite.
124 125 126 |
# File 'lib/dynamoid/transaction_write.rb', line 124 def initialize @actions = [] end |
Class Method Details
.execute ⇒ Object
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 |
# File 'lib/dynamoid/transaction_write.rb', line 108 def self.execute transaction = new begin yield transaction rescue StandardError => e transaction.rollback unless e.is_a?(Dynamoid::Errors::Rollback) raise e end else transaction.commit end end |
Instance Method Details
#commit ⇒ Object
Persist all the changes.
transaction = Dynamoid::TransactionWrite.new
# ...
transaction.commit
133 134 135 136 137 138 139 140 141 142 143 144 145 |
# File 'lib/dynamoid/transaction_write.rb', line 133 def commit actions_to_commit = @actions.reject(&:aborted?).reject(&:skipped?) return if actions_to_commit.empty? action_requests = actions_to_commit.map(&:action_request) Dynamoid.adapter.transact_write_items(action_requests) actions_to_commit.each(&:on_commit) nil rescue Aws::Errors::ServiceError run_on_rollback_callbacks raise end |
#create(model_class, attributes = {}, &block) ⇒ Dynamoid::Document
Create a model.
Dynamoid::TransactionWrite.execute do |t|
t.create(User, name: 'A')
end
Accepts both Hash and Array of Hashes and can create several models.
Dynamoid::TransactionWrite.execute do |t|
t.create(User, [{name: 'A'}, {name: 'B'}, {name: 'C'}])
end
Instantiates a model and pass it into an optional block to set other attributes.
Dynamoid::TransactionWrite.execute do |t|
t.create(User, name: 'A') do |user|
user.initialize_roles
end
end
Validates model and runs callbacks.
293 294 295 296 297 298 299 300 301 302 303 |
# File 'lib/dynamoid/transaction_write.rb', line 293 def create(model_class, attributes = {}, &block) if attributes.is_a? Array attributes.map do |attr| action = Dynamoid::TransactionWrite::Create.new(model_class, attr, raise_error: false, &block) register_action action end else action = Dynamoid::TransactionWrite::Create.new(model_class, attributes, raise_error: false, &block) register_action action end end |
#create!(model_class, attributes = {}, &block) ⇒ Dynamoid::Document
Create a model.
Dynamoid::TransactionWrite.execute do |t|
t.create!(User, name: 'A')
end
Accepts both Hash and Array of Hashes and can create several models.
Dynamoid::TransactionWrite.execute do |t|
t.create!(User, [{name: 'A'}, {name: 'B'}, {name: 'C'}])
end
Instantiates a model and pass it into an optional block to set other attributes.
Dynamoid::TransactionWrite.execute do |t|
t.create!(User, name: 'A') do |user|
user.initialize_roles
end
end
Validates model and runs callbacks.
254 255 256 257 258 259 260 261 262 263 264 |
# File 'lib/dynamoid/transaction_write.rb', line 254 def create!(model_class, attributes = {}, &block) if attributes.is_a? Array attributes.map do |attr| action = Dynamoid::TransactionWrite::Create.new(model_class, attr, raise_error: true, &block) register_action action end else action = Dynamoid::TransactionWrite::Create.new(model_class, attributes, raise_error: true, &block) register_action action end end |
#delete(model_or_model_class, hash_key = nil, range_key = nil) ⇒ Dynamoid::Document
Delete a model.
Can be called either with a model:
Dynamoid::TransactionWrite.execute do |t|
t.delete(user)
end
or with a primary key:
Dynamoid::TransactionWrite.execute do |t|
t.delete(User, user_id)
end
Raise MissingRangeKey
if a range key is declared but not passed as argument.
417 418 419 420 421 422 423 424 |
# File 'lib/dynamoid/transaction_write.rb', line 417 def delete(model_or_model_class, hash_key = nil, range_key = nil) action = if model_or_model_class.is_a? Class Dynamoid::TransactionWrite::DeleteWithPrimaryKey.new(model_or_model_class, hash_key, range_key) else Dynamoid::TransactionWrite::DeleteWithInstance.new(model_or_model_class) end register_action action end |
#destroy(model) ⇒ Dynamoid::Document
Delete a model.
Runs callbacks.
446 447 448 449 |
# File 'lib/dynamoid/transaction_write.rb', line 446 def destroy(model) action = Dynamoid::TransactionWrite::Destroy.new(model, raise_error: false) register_action action end |
#destroy!(model) ⇒ Dynamoid::Document|false
Delete a model.
Runs callbacks.
Raises Dynamoid::Errors::RecordNotDestroyed
exception if model deleting failed (e.g. aborted by a callback).
435 436 437 438 |
# File 'lib/dynamoid/transaction_write.rb', line 435 def destroy!(model) action = Dynamoid::TransactionWrite::Destroy.new(model, raise_error: true) register_action action end |
#rollback ⇒ Object
147 148 149 |
# File 'lib/dynamoid/transaction_write.rb', line 147 def rollback run_on_rollback_callbacks end |
#save(model, **options) ⇒ true|false
Create new model or persist changes in already existing one.
Run the validation and callbacks. Raise Dynamoid::Errors::DocumentNotValid
unless this object is valid.
user = User.new
Dynamoid::TransactionWrite.execute do |t|
t.save(user)
end
Validation can be skipped with validate: false option:
user = User.new(age: -1)
Dynamoid::TransactionWrite.execute do |t|
t.save(user, validate: false)
end
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.
If a model is new and hash key (id
by default) is not assigned yet it was assigned implicitly with random UUID value.
When a model is not persisted - its id should have unique value. Otherwise a transaction will be rolled back.
222 223 224 225 |
# File 'lib/dynamoid/transaction_write.rb', line 222 def save(model, **) action = Dynamoid::TransactionWrite::Save.new(model, **, raise_error: false) register_action action end |
#save!(model, **options) ⇒ true|false
Create new model or persist changes in already existing one.
Run the validation and callbacks. Returns true
if saving is successful and false
otherwise.
user = User.new
Dynamoid::TransactionWrite.execute do |t|
t.save!(user)
end
Validation can be skipped with validate: false option:
user = User.new(age: -1)
Dynamoid::TransactionWrite.execute do |t|
t.save!(user, validate: false)
end
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.
If a model is new and hash key (id
by default) is not assigned yet it was assigned implicitly with random UUID value.
When a model is not persisted - its id should have unique value. Otherwise a transaction will be rolled back.
184 185 186 187 |
# File 'lib/dynamoid/transaction_write.rb', line 184 def save!(model, **) action = Dynamoid::TransactionWrite::Save.new(model, **, raise_error: true) register_action action end |
#update_attributes(model, attributes) ⇒ true|false
Update multiple attributes at once.
Dynamoid::TransactionWrite.execute do |t|
t.update_attributes(user, age: 27, last_name: 'Tylor')
end
Returns true
if saving is successful and false
otherwise.
373 374 375 376 |
# File 'lib/dynamoid/transaction_write.rb', line 373 def update_attributes(model, attributes) action = Dynamoid::TransactionWrite::UpdateAttributes.new(model, attributes, raise_error: false) register_action action end |
#update_attributes!(model, attributes) ⇒ Object
Update multiple attributes at once.
Returns true
if saving is successful and false
otherwise.
Dynamoid::TransactionWrite.execute do |t|
t.update_attributes(user, age: 27, last_name: 'Tylor')
end
Raises a Dynamoid::Errors::DocumentNotValid
exception if some vaidation fails.
392 393 394 395 |
# File 'lib/dynamoid/transaction_write.rb', line 392 def update_attributes!(model, attributes) action = Dynamoid::TransactionWrite::UpdateAttributes.new(model, attributes, raise_error: true) register_action action end |
#update_fields(model_class, hash_key, range_key = nil, attributes) ⇒ nil
Update document.
Doesn’t run validations and callbacks.
Dynamoid::TransactionWrite.execute do |t|
t.update_fields(User, '1', age: 26)
end
If range key is declared for a model it should be passed as well:
Dynamoid::TransactionWrite.execute do |t|
t.update_fields(User, '1', 'Tylor', age: 26)
end
Raises a Dynamoid::Errors::UnknownAttribute
exception if any of the attributes is not declared in the model class.
356 357 358 359 |
# File 'lib/dynamoid/transaction_write.rb', line 356 def update_fields(model_class, hash_key, range_key = nil, attributes) # rubocop:disable Style/OptionalArguments action = Dynamoid::TransactionWrite::UpdateFields.new(model_class, hash_key, range_key, attributes) register_action action end |
#upsert(model_class, hash_key, range_key = nil, attributes) ⇒ nil
Update an existing document or create a new one.
If a document with specified hash and range keys doesn’t exist it creates a new document with specified attributes. Doesn’t run validations and callbacks.
Dynamoid::TransactionWrite.execute do |t|
t.upsert(User, '1', age: 26)
end
If range key is declared for a model it should be passed as well:
Dynamoid::TransactionWrite.execute do |t|
t.upsert(User, '1', 'Tylor', age: 26)
end
Raises a Dynamoid::Errors::UnknownAttribute
exception if any of the attributes is not declared in the model class.
329 330 331 332 |
# File 'lib/dynamoid/transaction_write.rb', line 329 def upsert(model_class, hash_key, range_key = nil, attributes) # rubocop:disable Style/OptionalArguments action = Dynamoid::TransactionWrite::Upsert.new(model_class, hash_key, range_key, attributes) register_action action end |