dynamoid 3.11.0 → 3.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,6 +8,7 @@ require 'dynamoid/transaction_write/save'
8
8
  require 'dynamoid/transaction_write/update_fields'
9
9
  require 'dynamoid/transaction_write/update_attributes'
10
10
  require 'dynamoid/transaction_write/upsert'
11
+ require 'dynamoid/transaction_write/item_updater'
11
12
 
12
13
  module Dynamoid
13
14
  # The class +TransactionWrite+ provides means to perform multiple modifying
@@ -177,6 +178,26 @@ module Dynamoid
177
178
  # When a model is not persisted - its id should have unique value.
178
179
  # Otherwise a transaction will be rolled back.
179
180
  #
181
+ # Raises +Dynamoid::Errors::MissingHashKey+ if a model is already persisted
182
+ # and a partition key has value +nil+ and raises
183
+ # +Dynamoid::Errors::MissingRangeKey+ if a sort key is required but has
184
+ # value +nil+.
185
+ #
186
+ # There are the following differences between transactional and
187
+ # non-transactional +#save!+:
188
+ # - transactional +#save!+ doesn't support the +:touch+ option
189
+ # - transactional +#save!+ doesn't support +lock_version+ attribute so it
190
+ # will not be incremented and will not be checked to detect concurrent
191
+ # modification of a model and +Dynamoid::Errors::StaleObjectError+
192
+ # exception will not be raised
193
+ # - transactional +#save!+ doesn't raise +Dynamoid::Errors::RecordNotUnique+
194
+ # at saving new model when primary key is already used. A generic
195
+ # +Aws::DynamoDB::Errors::TransactionCanceledException+ is raised instead.
196
+ # - transactional +save!+ doesn't raise
197
+ # +Dynamoid::Errors::StaleObjectError+ when a model that is being updated
198
+ # was concurrently deleted
199
+ # - a table isn't created lazily if it doesn't exist yet
200
+ #
180
201
  # @param model [Dynamoid::Document] a model
181
202
  # @param options [Hash] (optional)
182
203
  # @option options [true|false] :validate validate a model or not - +true+ by default (optional)
@@ -215,6 +236,25 @@ module Dynamoid
215
236
  # When a model is not persisted - its id should have unique value.
216
237
  # Otherwise a transaction will be rolled back.
217
238
  #
239
+ # Raises +Dynamoid::Errors::MissingHashKey+ if a model is already persisted
240
+ # and a partition key has value +nil+ and raises
241
+ # +Dynamoid::Errors::MissingRangeKey+ if a sort key is required but has
242
+ # value +nil+.
243
+ #
244
+ # There are the following differences between transactional and
245
+ # non-transactional +#save+:
246
+ # - transactional +#save+ doesn't support the +:touch+ option
247
+ # - transactional +#save+ doesn't support +lock_version+ attribute so it
248
+ # will not be incremented and will not be checked to detect concurrent
249
+ # modification of a model and +Dynamoid::Errors::StaleObjectError+
250
+ # exception will not be raised
251
+ # - transactional +#save+ doesn't raise +Dynamoid::Errors::RecordNotUnique+
252
+ # at saving new model when primary key is already used. A generic
253
+ # +Aws::DynamoDB::Errors::TransactionCanceledException+ is raised instead.
254
+ # - transactional +save+ doesn't raise +Dynamoid::Errors::StaleObjectError+
255
+ # when a model that is being updated was concurrently deleted
256
+ # - a table isn't created lazily if it doesn't exist yet
257
+ #
218
258
  # @param model [Dynamoid::Document] a model
219
259
  # @param options [Hash] (optional)
220
260
  # @option options [true|false] :validate validate a model or not - +true+ by default (optional)
@@ -247,6 +287,20 @@ module Dynamoid
247
287
  #
248
288
  # Validates model and runs callbacks.
249
289
  #
290
+ # Raises +Dynamoid::Errors::MissingRangeKey+ if a sort key is required but
291
+ # not specified or has value +nil+.
292
+ #
293
+ # There are the following differences between transactional and
294
+ # non-transactional +#create+:
295
+ # - transactional +#create!+ doesn't support +lock_version+ attribute so it
296
+ # will not be incremented and will not be checked to detect concurrent
297
+ # modification of a model and +Dynamoid::Errors::StaleObjectError+
298
+ # exception will not be raised
299
+ # - transactional +#create!+ doesn't raise +Dynamoid::Errors::RecordNotUnique+
300
+ # at saving new model when primary key is already used. A generic
301
+ # +Aws::DynamoDB::Errors::TransactionCanceledException+ is raised instead.
302
+ # - a table isn't created lazily if it doesn't exist yet
303
+ #
250
304
  # @param model_class [Class] a model class which should be instantiated
251
305
  # @param attributes [Hash|Array<Hash>] attributes of a model
252
306
  # @param block [Proc] a block to process a model after initialization
@@ -286,6 +340,20 @@ module Dynamoid
286
340
  #
287
341
  # Validates model and runs callbacks.
288
342
  #
343
+ # Raises +Dynamoid::Errors::MissingRangeKey+ if a sort key is required but
344
+ # not specified or has value +nil+.
345
+ #
346
+ # There are the following differences between transactional and
347
+ # non-transactional +#create+:
348
+ # - transactional +#create+ doesn't support +lock_version+ attribute so it
349
+ # will not be incremented and will not be checked to detect concurrent
350
+ # modification of a model and +Dynamoid::Errors::StaleObjectError+
351
+ # exception will not be raised
352
+ # - transactional +#create+ doesn't raise +Dynamoid::Errors::RecordNotUnique+
353
+ # at saving new model when primary key is already used. A generic
354
+ # +Aws::DynamoDB::Errors::TransactionCanceledException+ is raised instead.
355
+ # - a table isn't created lazily if it doesn't exist yet
356
+ #
289
357
  # @param model_class [Class] a model class which should be instantiated
290
358
  # @param attributes [Hash|Array<Hash>] attributes of a model
291
359
  # @param block [Proc] a block to process a model after initialization
@@ -321,6 +389,17 @@ module Dynamoid
321
389
  # Raises a +Dynamoid::Errors::UnknownAttribute+ exception if any of the
322
390
  # attributes is not declared in the model class.
323
391
  #
392
+ # Raises +Dynamoid::Errors::MissingHashKey+ if a partition key has value
393
+ # +nil+ and +Dynamoid::Errors::MissingRangeKey+ if a sort key is required
394
+ # but has value +nil+.
395
+ #
396
+ # There are the following differences between transactional and
397
+ # non-transactional +#upsert+:
398
+ # - transactional +#upsert+ doesn't support conditions (that's +if+ and
399
+ # +unless_exists+ options)
400
+ # - transactional +#upsert+ doesn't return a document that was updated or
401
+ # created
402
+ #
324
403
  # @param model_class [Class] a model class
325
404
  # @param hash_key [Scalar value] hash key value
326
405
  # @param range_key [Scalar value] range key value (optional)
@@ -345,16 +424,79 @@ module Dynamoid
345
424
  # t.update_fields(User, '1', 'Tylor', age: 26)
346
425
  # end
347
426
  #
427
+ # Updates can also be performed in a block.
428
+ #
429
+ # Dynamoid::TransactionWrite.execute do |t|
430
+ # t.update_fields(User, 1) do |u|
431
+ # u.add(article_count: 1)
432
+ # u.delete(favorite_colors: 'green')
433
+ # u.set(age: 27, last_name: 'Tylor')
434
+ # end
435
+ # end
436
+ #
437
+ # Operation +add+ just adds a value for numeric attributes and join
438
+ # collections if attribute is a set.
439
+ #
440
+ # t.update_fields(User, 1) do |u|
441
+ # u.add(age: 1, followers_count: 5)
442
+ # u.add(hobbies: ['skying', 'climbing'])
443
+ # end
444
+ #
445
+ # Operation +delete+ is applied to collection attribute types and
446
+ # substructs one collection from another.
447
+ #
448
+ # t.update_fields(User, 1) do |u|
449
+ # u.delete(hobbies: ['skying'])
450
+ # end
451
+ #
452
+ # Operation +set+ just changes an attribute value:
453
+ #
454
+ # t.update_fields(User, 1) do |u|
455
+ # u.set(age: 21)
456
+ # end
457
+ #
458
+ # Operation +remove+ removes one or more attributes from an item.
459
+ #
460
+ # t.update_fields(User, 1) do |u|
461
+ # u.remove(:age)
462
+ # end
463
+ #
464
+ # All the operations work like +ADD+, +DELETE+, +REMOVE+, and +SET+ actions supported
465
+ # by +UpdateExpression+
466
+ # {parameter}[https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html]
467
+ # of +UpdateItem+ operation.
468
+ #
469
+ # It's atomic operations. So adding or deleting elements in a collection
470
+ # or incrementing or decrementing a numeric field is atomic and does not
471
+ # interfere with other write requests.
472
+ #
348
473
  # Raises a +Dynamoid::Errors::UnknownAttribute+ exception if any of the
349
474
  # attributes is not declared in the model class.
350
475
  #
476
+ # Raises +Dynamoid::Errors::MissingHashKey+ if a partition key has value
477
+ # +nil+ and +Dynamoid::Errors::MissingRangeKey+ if a sort key is required
478
+ # but has value +nil+.
479
+ #
480
+ # There are the following differences between transactional and
481
+ # non-transactional +#update_fields+:
482
+ # - transactional +#update_fields+ doesn't support conditions (that's +if+
483
+ # and +unless_exists+ options)
484
+ # - transactional +#update_fields+ doesn't return a document that was
485
+ # updated or created
486
+ #
351
487
  # @param model_class [Class] a model class
352
488
  # @param hash_key [Scalar value] hash key value
353
489
  # @param range_key [Scalar value] range key value (optional)
354
490
  # @param attributes [Hash]
355
491
  # @return [nil]
356
- def update_fields(model_class, hash_key, range_key = nil, attributes) # rubocop:disable Style/OptionalArguments
357
- action = Dynamoid::TransactionWrite::UpdateFields.new(model_class, hash_key, range_key, attributes)
492
+ def update_fields(model_class, hash_key, range_key = nil, attributes = nil, &block)
493
+ # given no attributes, but there may be a block
494
+ if range_key.is_a?(Hash) && !attributes
495
+ attributes = range_key
496
+ range_key = nil
497
+ end
498
+
499
+ action = Dynamoid::TransactionWrite::UpdateFields.new(model_class, hash_key, range_key, attributes, &block)
358
500
  register_action action
359
501
  end
360
502
 
@@ -367,6 +509,21 @@ module Dynamoid
367
509
  # Returns +true+ if saving is successful and +false+
368
510
  # otherwise.
369
511
  #
512
+ # Raises +Dynamoid::Errors::MissingHashKey+ if a partition key has value
513
+ # +nil+ and raises +Dynamoid::Errors::MissingRangeKey+ if a sort key is
514
+ # required but has value +nil+.
515
+ #
516
+ # There are the following differences between transactional and
517
+ # non-transactional +#update_attributes+:
518
+ # - transactional +#update_attributes+ doesn't support +lock_version+ attribute so it
519
+ # will not be incremented and will not be checked to detect concurrent
520
+ # modification of a model and +Dynamoid::Errors::StaleObjectError+
521
+ # exception will not be raised
522
+ # - transactional +update_attributes+ doesn't raise
523
+ # +Dynamoid::Errors::StaleObjectError+ when a model that is being updated
524
+ # was concurrently deleted
525
+ # - a table isn't created lazily if it doesn't exist yet
526
+ #
370
527
  # @param model [Dynamoid::Document] a model
371
528
  # @param attributes [Hash] a hash of attributes to update
372
529
  # @return [true|false] Whether updating successful or not
@@ -387,6 +544,21 @@ module Dynamoid
387
544
  # Raises a +Dynamoid::Errors::DocumentNotValid+ exception if some vaidation
388
545
  # fails.
389
546
  #
547
+ # Raises +Dynamoid::Errors::MissingHashKey+ if a partition key has value
548
+ # +nil+ and raises +Dynamoid::Errors::MissingRangeKey+ if a sort key is
549
+ # required but has value +nil+.
550
+ #
551
+ # There are the following differences between transactional and
552
+ # non-transactional +#update_attributes!+:
553
+ # - transactional +#update_attributes!+ doesn't support +lock_version+
554
+ # attribute so it will not be incremented and will not be checked to detect
555
+ # concurrent modification of a model and
556
+ # +Dynamoid::Errors::StaleObjectError+ exception will not be raised
557
+ # - transactional +update_attributes!+ doesn't raise
558
+ # +Dynamoid::Errors::StaleObjectError+ when a model that is being updated
559
+ # was concurrently deleted
560
+ # - a table isn't created lazily if it doesn't exist yet
561
+ #
390
562
  # @param model [Dynamoid::Document] a model
391
563
  # @param attributes [Hash] a hash of attributes to update
392
564
  def update_attributes!(model, attributes)
@@ -408,7 +580,18 @@ module Dynamoid
408
580
  # t.delete(User, user_id)
409
581
  # end
410
582
  #
411
- # Raise +MissingRangeKey+ if a range key is declared but not passed as argument.
583
+ # Raises +Dynamoid::Errors::MissingHashKey+ if a partition key has value
584
+ # +nil+ and raises +Dynamoid::Errors::MissingRangeKey+ if a sort key is
585
+ # required but has value +nil+.
586
+ #
587
+ # There are the following differences between transactional and
588
+ # non-transactional +#delete+: TBD
589
+ # - transactional +#delete+ doesn't support +lock_version+ attribute so it
590
+ # will not be incremented and will not be checked to detect concurrent
591
+ # modification of a model and +Dynamoid::Errors::StaleObjectError+
592
+ # exception will not be raised
593
+ # - transactional +#delete+ doesn't disassociate a model from associated ones
594
+ # if there is any
412
595
  #
413
596
  # @param model_or_model_class [Class|Dynamoid::Document] either model or model class
414
597
  # @param hash_key [Scalar value] hash key value
@@ -430,6 +613,19 @@ module Dynamoid
430
613
  # Raises +Dynamoid::Errors::RecordNotDestroyed+ exception if model deleting
431
614
  # failed (e.g. aborted by a callback).
432
615
  #
616
+ # Raises +Dynamoid::Errors::MissingHashKey+ if a partition key has value
617
+ # +nil+ and raises +Dynamoid::Errors::MissingRangeKey+ if a sort key is
618
+ # required but has value +nil+.
619
+ #
620
+ # There are the following differences between transactional and
621
+ # non-transactional +#destroy!+:
622
+ # - transactional +#destroy!+ doesn't support +lock_version+ attribute so it
623
+ # will not be incremented and will not be checked to detect concurrent
624
+ # modification of a model and +Dynamoid::Errors::StaleObjectError+
625
+ # exception will not be raised
626
+ # - transactional +#destroy!+ doesn't disassociate a model from associated ones
627
+ # if there are association declared in the model class
628
+ #
433
629
  # @param model [Dynamoid::Document] a model
434
630
  # @return [Dynamoid::Document|false] returns self if destoying is succefull, +false+ otherwise
435
631
  def destroy!(model)
@@ -441,6 +637,19 @@ module Dynamoid
441
637
  #
442
638
  # Runs callbacks.
443
639
  #
640
+ # Raises +Dynamoid::Errors::MissingHashKey+ if a partition key has value
641
+ # +nil+ and raises +Dynamoid::Errors::MissingRangeKey+ if a sort key is
642
+ # required but has value +nil+.
643
+ #
644
+ # There are the following differences between transactional and
645
+ # non-transactional +#destroy+:
646
+ # - transactional +#destroy+ doesn't support +lock_version+ attribute so it
647
+ # will not be incremented and will not be checked to detect concurrent
648
+ # modification of a model and +Dynamoid::Errors::StaleObjectError+
649
+ # exception will not be raised
650
+ # - transactional +#destroy+ doesn't disassociate a model from associated ones
651
+ # if there are association declared in the model class
652
+ #
444
653
  # @param model [Dynamoid::Document] a model
445
654
  # @return [Dynamoid::Document] self
446
655
  def destroy(model)
@@ -31,17 +31,20 @@ module Dynamoid
31
31
  #
32
32
  # @private
33
33
  # @since 0.2.0
34
- def save!
35
- raise Dynamoid::Errors::DocumentNotValid, self unless valid?
34
+ def save!(options = {})
35
+ unless valid?
36
+ raise Dynamoid::Errors::DocumentNotValid, self
37
+ end
36
38
 
37
- save(validate: false)
38
- self
39
+ super
39
40
  end
40
41
 
41
42
  def update_attribute(attribute, value)
42
43
  write_attribute(attribute, value)
43
44
  save(validate: false)
44
45
  self
46
+ rescue Dynamoid::Errors::StaleObjectError
47
+ self
45
48
  end
46
49
 
47
50
  module ClassMethods
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Dynamoid
4
- VERSION = '3.11.0'
4
+ VERSION = '3.12.1'
5
5
  end
data/lib/dynamoid.rb CHANGED
@@ -35,6 +35,7 @@ require 'dynamoid/loadable'
35
35
  require 'dynamoid/components'
36
36
  require 'dynamoid/document'
37
37
  require 'dynamoid/adapter'
38
+ require 'dynamoid/transaction_read'
38
39
  require 'dynamoid/transaction_write'
39
40
 
40
41
  require 'dynamoid/tasks/database'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dynamoid
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.11.0
4
+ version: 3.12.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Josh Symonds
@@ -21,7 +21,7 @@ authors:
21
21
  autorequire:
22
22
  bindir: bin
23
23
  cert_chain: []
24
- date: 2025-01-12 00:00:00.000000000 Z
24
+ date: 2025-08-23 00:00:00.000000000 Z
25
25
  dependencies:
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: activemodel
@@ -242,12 +242,15 @@ files:
242
242
  - lib/dynamoid/tasks.rb
243
243
  - lib/dynamoid/tasks/database.rake
244
244
  - lib/dynamoid/tasks/database.rb
245
+ - lib/dynamoid/transaction_read.rb
246
+ - lib/dynamoid/transaction_read/find.rb
245
247
  - lib/dynamoid/transaction_write.rb
246
248
  - lib/dynamoid/transaction_write/base.rb
247
249
  - lib/dynamoid/transaction_write/create.rb
248
250
  - lib/dynamoid/transaction_write/delete_with_instance.rb
249
251
  - lib/dynamoid/transaction_write/delete_with_primary_key.rb
250
252
  - lib/dynamoid/transaction_write/destroy.rb
253
+ - lib/dynamoid/transaction_write/item_updater.rb
251
254
  - lib/dynamoid/transaction_write/save.rb
252
255
  - lib/dynamoid/transaction_write/update_attributes.rb
253
256
  - lib/dynamoid/transaction_write/update_fields.rb
@@ -261,10 +264,10 @@ licenses:
261
264
  - MIT
262
265
  metadata:
263
266
  homepage_uri: http://github.com/Dynamoid/dynamoid
264
- source_code_uri: https://github.com/Dynamoid/dynamoid/tree/v3.11.0
265
- changelog_uri: https://github.com/Dynamoid/dynamoid/blob/v3.11.0/CHANGELOG.md
267
+ source_code_uri: https://github.com/Dynamoid/dynamoid/tree/v3.12.1
268
+ changelog_uri: https://github.com/Dynamoid/dynamoid/blob/v3.12.1/CHANGELOG.md
266
269
  bug_tracker_uri: https://github.com/Dynamoid/dynamoid/issues
267
- documentation_uri: https://www.rubydoc.info/gems/dynamoid/3.11.0
270
+ documentation_uri: https://www.rubydoc.info/gems/dynamoid/3.12.1
268
271
  funding_uri: https://opencollective.com/dynamoid
269
272
  wiki_uri: https://github.com/Dynamoid/dynamoid/wiki
270
273
  rubygems_mfa_required: 'true'