dynamoid 3.11.0 → 3.13.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +39 -3
- data/README.md +94 -14
- data/SECURITY.md +6 -6
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/batch_get_item.rb +3 -1
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/item_updater.rb +1 -1
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/query.rb +4 -1
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/scan.rb +4 -1
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/table.rb +13 -0
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3.rb +24 -9
- data/lib/dynamoid/config.rb +1 -0
- data/lib/dynamoid/criteria/chain.rb +11 -3
- data/lib/dynamoid/dirty.rb +22 -11
- data/lib/dynamoid/dumping.rb +3 -3
- data/lib/dynamoid/errors.rb +16 -1
- data/lib/dynamoid/fields/declare.rb +1 -1
- data/lib/dynamoid/fields.rb +44 -4
- data/lib/dynamoid/finders.rb +44 -19
- data/lib/dynamoid/persistence/inc.rb +30 -13
- data/lib/dynamoid/persistence/save.rb +24 -12
- data/lib/dynamoid/persistence/update_fields.rb +18 -5
- data/lib/dynamoid/persistence/update_validations.rb +3 -3
- data/lib/dynamoid/persistence/upsert.rb +17 -4
- data/lib/dynamoid/persistence.rb +273 -19
- data/lib/dynamoid/transaction_read/find.rb +137 -0
- data/lib/dynamoid/transaction_read.rb +146 -0
- data/lib/dynamoid/transaction_write/base.rb +12 -0
- data/lib/dynamoid/transaction_write/delete_with_instance.rb +7 -2
- data/lib/dynamoid/transaction_write/delete_with_primary_key.rb +7 -2
- data/lib/dynamoid/transaction_write/destroy.rb +10 -5
- data/lib/dynamoid/transaction_write/item_updater.rb +60 -0
- data/lib/dynamoid/transaction_write/save.rb +22 -9
- data/lib/dynamoid/transaction_write/update_fields.rb +176 -31
- data/lib/dynamoid/transaction_write/upsert.rb +23 -6
- data/lib/dynamoid/transaction_write.rb +212 -3
- data/lib/dynamoid/validations.rb +15 -4
- data/lib/dynamoid/version.rb +1 -1
- data/lib/dynamoid.rb +1 -0
- metadata +9 -9
data/lib/dynamoid/persistence.rb
CHANGED
|
@@ -28,9 +28,16 @@ module Dynamoid
|
|
|
28
28
|
|
|
29
29
|
module ClassMethods
|
|
30
30
|
def table_name
|
|
31
|
-
|
|
31
|
+
return @table_name if @table_name
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
if options[:arn]
|
|
34
|
+
@table_name = options[:arn]
|
|
35
|
+
return @table_name
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
base_name = options[:name] || base_class.name.split('::').last.downcase.pluralize
|
|
39
|
+
namespace = Dynamoid::Config.namespace.to_s
|
|
40
|
+
@table_name = [namespace, base_name].reject(&:empty?).join('_')
|
|
34
41
|
end
|
|
35
42
|
|
|
36
43
|
# Create a table.
|
|
@@ -185,6 +192,9 @@ module Dynamoid
|
|
|
185
192
|
#
|
|
186
193
|
# Validates model and runs callbacks.
|
|
187
194
|
#
|
|
195
|
+
# Raises +Dynamoid::Errors::MissingRangeKey+ if a sort key is required
|
|
196
|
+
# but not specified or has value +nil+.
|
|
197
|
+
#
|
|
188
198
|
# @param attrs [Hash|Array<Hash>] Attributes of a model
|
|
189
199
|
# @param block [Proc] Block to process a document after initialization
|
|
190
200
|
# @return [Dynamoid::Document] The created document
|
|
@@ -206,6 +216,9 @@ module Dynamoid
|
|
|
206
216
|
# Raises an exception +Dynamoid::Errors::DocumentNotValid+ if validation
|
|
207
217
|
# failed.
|
|
208
218
|
#
|
|
219
|
+
# If any of the +before_*+ callbacks throws +:abort+ the creation is
|
|
220
|
+
# cancelled and +create!+ raises +Dynamoid::Errors::RecordNotSaved+.
|
|
221
|
+
#
|
|
209
222
|
# Accepts both Hash and Array of Hashes and can create several
|
|
210
223
|
# models.
|
|
211
224
|
#
|
|
@@ -220,6 +233,9 @@ module Dynamoid
|
|
|
220
233
|
#
|
|
221
234
|
# Validates model and runs callbacks.
|
|
222
235
|
#
|
|
236
|
+
# Raises +Dynamoid::Errors::MissingRangeKey+ if a sort key is required
|
|
237
|
+
# but not specified or has value +nil+.
|
|
238
|
+
#
|
|
223
239
|
# @param attrs [Hash|Array<Hash>] Attributes with which to create the object.
|
|
224
240
|
# @param block [Proc] Block to process a document after initialization
|
|
225
241
|
# @return [Dynamoid::Document] The created document
|
|
@@ -311,10 +327,16 @@ module Dynamoid
|
|
|
311
327
|
# Raises a +Dynamoid::Errors::UnknownAttribute+ exception if any of the
|
|
312
328
|
# attributes is not on the model
|
|
313
329
|
#
|
|
330
|
+
# Raises +Dynamoid::Errors::MissingHashKey+ if a partition key has value
|
|
331
|
+
# +nil+ and +Dynamoid::Errors::MissingRangeKey+ if a sort key is required
|
|
332
|
+
# but has value +nil+.
|
|
333
|
+
#
|
|
314
334
|
# @param hash_key_value [Scalar value] hash key
|
|
315
335
|
# @param range_key_value [Scalar value] range key (optional)
|
|
316
336
|
# @param attrs [Hash]
|
|
317
337
|
# @param conditions [Hash] (optional)
|
|
338
|
+
# @option conditions [Hash] :if conditions on attribute values
|
|
339
|
+
# @option conditions [Hash] :unless_exists conditions on attributes presence
|
|
318
340
|
# @return [Dynamoid::Document|nil] Updated document
|
|
319
341
|
def update_fields(hash_key_value, range_key_value = nil, attrs = {}, conditions = {})
|
|
320
342
|
optional_params = [range_key_value, attrs, conditions].compact
|
|
@@ -369,10 +391,16 @@ module Dynamoid
|
|
|
369
391
|
# Raises a +Dynamoid::Errors::UnknownAttribute+ exception if any of the
|
|
370
392
|
# attributes is not declared in the model class.
|
|
371
393
|
#
|
|
394
|
+
# Raises +Dynamoid::Errors::MissingHashKey+ if a partition key has value
|
|
395
|
+
# +nil+ and raises +Dynamoid::Errors::MissingRangeKey+ if a sort key is
|
|
396
|
+
# required but has value +nil+.
|
|
397
|
+
#
|
|
372
398
|
# @param hash_key_value [Scalar value] hash key
|
|
373
399
|
# @param range_key_value [Scalar value] range key (optional)
|
|
374
400
|
# @param attrs [Hash]
|
|
375
401
|
# @param conditions [Hash] (optional)
|
|
402
|
+
# @option conditions [Hash] :if conditions on attribute values
|
|
403
|
+
# @option conditions [Hash] :unless_exists conditions on attributes presence
|
|
376
404
|
# @return [Dynamoid::Document|nil] Updated document
|
|
377
405
|
def upsert(hash_key_value, range_key_value = nil, attrs = {}, conditions = {})
|
|
378
406
|
optional_params = [range_key_value, attrs, conditions].compact
|
|
@@ -426,9 +454,92 @@ module Dynamoid
|
|
|
426
454
|
# @option counters [true | Symbol | Array<Symbol>] :touch to update update_at attribute and optionally the specified ones
|
|
427
455
|
# @return [Model class] self
|
|
428
456
|
def inc(hash_key_value, range_key_value = nil, counters)
|
|
457
|
+
# It's similar to Rails' #update_counters.
|
|
429
458
|
Inc.call(self, hash_key_value, range_key_value, counters)
|
|
430
459
|
self
|
|
431
460
|
end
|
|
461
|
+
|
|
462
|
+
# Delete a model by a given primary key.
|
|
463
|
+
#
|
|
464
|
+
# Raises +Dynamoid::Errors::MissingHashKey+ if a partition key has value
|
|
465
|
+
# +nil+ and raises +Dynamoid::Errors::MissingRangeKey+ if a sort key is
|
|
466
|
+
# required but has value +nil+ or is missing.
|
|
467
|
+
#
|
|
468
|
+
# @param ids [String|Array] primary key or an array of primary keys
|
|
469
|
+
# @return nil
|
|
470
|
+
#
|
|
471
|
+
# @example Delete a model by given partition key:
|
|
472
|
+
# User.delete(user_id)
|
|
473
|
+
#
|
|
474
|
+
# @example Delete a model by given partition and sort keys:
|
|
475
|
+
# User.delete(user_id, sort_key)
|
|
476
|
+
#
|
|
477
|
+
# @example Delete multiple models by given partition keys:
|
|
478
|
+
# User.delete([id1, id2, id3])
|
|
479
|
+
#
|
|
480
|
+
# @example Delete multiple models by given partition and sort keys:
|
|
481
|
+
# User.delete([[id1, sk1], [id2, sk2], [id3, sk3]])
|
|
482
|
+
def delete(*ids)
|
|
483
|
+
if ids.empty?
|
|
484
|
+
raise Dynamoid::Errors::MissingHashKey
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
if ids[0].is_a?(Array)
|
|
488
|
+
# given multiple keys
|
|
489
|
+
# Model.delete([id1, id2, id3])
|
|
490
|
+
keys = ids[0] # ignore other arguments
|
|
491
|
+
|
|
492
|
+
ids = []
|
|
493
|
+
if self.range_key
|
|
494
|
+
# compound primary key
|
|
495
|
+
# expect [hash key, range key] pairs
|
|
496
|
+
|
|
497
|
+
range_key = []
|
|
498
|
+
|
|
499
|
+
# assume all elements are pairs, that's arrays
|
|
500
|
+
keys.each do |pk, sk|
|
|
501
|
+
raise Dynamoid::Errors::MissingHashKey if pk.nil?
|
|
502
|
+
raise Dynamoid::Errors::MissingRangeKey if self.range_key && sk.nil?
|
|
503
|
+
|
|
504
|
+
partition_key_dumped = cast_and_dump(hash_key, pk)
|
|
505
|
+
sort_key_dumped = cast_and_dump(self.range_key, sk)
|
|
506
|
+
|
|
507
|
+
ids << partition_key_dumped
|
|
508
|
+
range_key << sort_key_dumped
|
|
509
|
+
end
|
|
510
|
+
else
|
|
511
|
+
# simple primary key
|
|
512
|
+
|
|
513
|
+
range_key = nil
|
|
514
|
+
|
|
515
|
+
keys.each do |pk|
|
|
516
|
+
raise Dynamoid::Errors::MissingHashKey if pk.nil?
|
|
517
|
+
|
|
518
|
+
partition_key_dumped = cast_and_dump(hash_key, pk)
|
|
519
|
+
ids << partition_key_dumped
|
|
520
|
+
end
|
|
521
|
+
end
|
|
522
|
+
|
|
523
|
+
options = range_key ? { range_key: range_key } : {}
|
|
524
|
+
Dynamoid.adapter.delete(table_name, ids, options)
|
|
525
|
+
else
|
|
526
|
+
# given single primary key:
|
|
527
|
+
# Model.delete(partition_key)
|
|
528
|
+
# Model.delete(partition_key, sort_key)
|
|
529
|
+
|
|
530
|
+
partition_key, sort_key = ids
|
|
531
|
+
|
|
532
|
+
raise Dynamoid::Errors::MissingHashKey if partition_key.nil?
|
|
533
|
+
raise Dynamoid::Errors::MissingRangeKey if range_key? && sort_key.nil?
|
|
534
|
+
|
|
535
|
+
options = sort_key ? { range_key: cast_and_dump(self.range_key, sort_key) } : {}
|
|
536
|
+
partition_key_dumped = cast_and_dump(hash_key, partition_key)
|
|
537
|
+
|
|
538
|
+
Dynamoid.adapter.delete(table_name, partition_key_dumped, options)
|
|
539
|
+
end
|
|
540
|
+
|
|
541
|
+
nil
|
|
542
|
+
end
|
|
432
543
|
end
|
|
433
544
|
|
|
434
545
|
# Update document timestamps.
|
|
@@ -519,7 +630,8 @@ module Dynamoid
|
|
|
519
630
|
# If a model is new and hash key (+id+ by default) is not assigned yet
|
|
520
631
|
# it was assigned implicitly with random UUID value.
|
|
521
632
|
#
|
|
522
|
-
# If +lock_version+ attribute is declared it will be incremented. If it's
|
|
633
|
+
# If +lock_version+ attribute is declared it will be incremented. If it's
|
|
634
|
+
# blank then it will be initialized with 1.
|
|
523
635
|
#
|
|
524
636
|
# +save+ method call raises +Dynamoid::Errors::RecordNotUnique+ exception
|
|
525
637
|
# if primary key (hash key + optional range key) already exists in a
|
|
@@ -530,6 +642,11 @@ module Dynamoid
|
|
|
530
642
|
# already changed concurrently and +lock_version+ was consequently
|
|
531
643
|
# increased.
|
|
532
644
|
#
|
|
645
|
+
# Raises +Dynamoid::Errors::MissingHashKey+ if a model is already persisted
|
|
646
|
+
# and a partition key has value +nil+ and raises
|
|
647
|
+
# +Dynamoid::Errors::MissingRangeKey+ if a sort key is required but has
|
|
648
|
+
# value +nil+.
|
|
649
|
+
#
|
|
533
650
|
# When a table is not created yet the first +save+ method call will create
|
|
534
651
|
# a table. It's useful in test environment to avoid explicit table
|
|
535
652
|
# creation.
|
|
@@ -553,6 +670,88 @@ module Dynamoid
|
|
|
553
670
|
end
|
|
554
671
|
end
|
|
555
672
|
|
|
673
|
+
# Create new model or persist changes.
|
|
674
|
+
#
|
|
675
|
+
# Run the validation and callbacks. Raises
|
|
676
|
+
# +Dynamoid::Errors::DocumentNotValid+ is validation fails.
|
|
677
|
+
#
|
|
678
|
+
# user = User.create
|
|
679
|
+
#
|
|
680
|
+
# user.age = 26
|
|
681
|
+
# user.save! # => user
|
|
682
|
+
#
|
|
683
|
+
# Validation can be skipped with +validate: false+ option:
|
|
684
|
+
#
|
|
685
|
+
# user = User.new(age: -1)
|
|
686
|
+
# user.save!(validate: false) # => user
|
|
687
|
+
#
|
|
688
|
+
# If any of the +before_*+ callbacks throws +:abort+ the saving is
|
|
689
|
+
# cancelled and +save!+ raises +Dynamoid::Errors::RecordNotSaved+.
|
|
690
|
+
#
|
|
691
|
+
# +save!+ by default sets timestamps attributes - +created_at+ and
|
|
692
|
+
# +updated_at+ when creates new model and updates +updated_at+ attribute
|
|
693
|
+
# when updates already existing one.
|
|
694
|
+
#
|
|
695
|
+
# Changing +updated_at+ attribute at updating a model can be skipped with
|
|
696
|
+
# +touch: false+ option:
|
|
697
|
+
#
|
|
698
|
+
# user.save!(touch: false)
|
|
699
|
+
#
|
|
700
|
+
# If a model is new and hash key (+id+ by default) is not assigned yet
|
|
701
|
+
# it was assigned implicitly with random UUID value.
|
|
702
|
+
#
|
|
703
|
+
# If +lock_version+ attribute is declared it will be incremented. If it's
|
|
704
|
+
# blank then it will be initialized with 1.
|
|
705
|
+
#
|
|
706
|
+
# +save!+ method call raises +Dynamoid::Errors::RecordNotUnique+ exception
|
|
707
|
+
# if primary key (hash key + optional range key) already exists in a
|
|
708
|
+
# table.
|
|
709
|
+
#
|
|
710
|
+
# +save!+ method call raises +Dynamoid::Errors::StaleObjectError+ exception
|
|
711
|
+
# if there is +lock_version+ attribute and the document in a table was
|
|
712
|
+
# already changed concurrently and +lock_version+ was consequently
|
|
713
|
+
# increased.
|
|
714
|
+
#
|
|
715
|
+
# +save!+ method call raises +Dynamoid::Errors::RecordNotSaved+ exception
|
|
716
|
+
# if some callback aborted execution.
|
|
717
|
+
#
|
|
718
|
+
# Raises +Dynamoid::Errors::MissingHashKey+ if a model is already persisted
|
|
719
|
+
# and a partition key has value +nil+ and raises
|
|
720
|
+
# +Dynamoid::Errors::MissingRangeKey+ if a sort key is required but has
|
|
721
|
+
# value +nil+.
|
|
722
|
+
#
|
|
723
|
+
# When a table is not created yet the first +save!+ method call will create
|
|
724
|
+
# a table. It's useful in test environment to avoid explicit table
|
|
725
|
+
# creation.
|
|
726
|
+
#
|
|
727
|
+
# @param options [Hash] (optional)
|
|
728
|
+
# @option options [true|false] :validate validate a model or not - +true+ by default (optional)
|
|
729
|
+
# @option options [true|false] :touch update tiemstamps fields or not - +true+ by default (optional)
|
|
730
|
+
# @return [true|false] Whether saving successful or not
|
|
731
|
+
def save!(options = {})
|
|
732
|
+
# validation is handled in the Validation module
|
|
733
|
+
|
|
734
|
+
if Dynamoid.config.create_table_on_save
|
|
735
|
+
self.class.create_table(sync: true)
|
|
736
|
+
end
|
|
737
|
+
|
|
738
|
+
create_or_update = new_record? ? :create : :update
|
|
739
|
+
aborted = true
|
|
740
|
+
|
|
741
|
+
run_callbacks(:save) do
|
|
742
|
+
run_callbacks(create_or_update) do
|
|
743
|
+
aborted = false
|
|
744
|
+
Save.call(self, touch: options[:touch])
|
|
745
|
+
end
|
|
746
|
+
end
|
|
747
|
+
|
|
748
|
+
if aborted
|
|
749
|
+
raise Dynamoid::Errors::RecordNotSaved, self
|
|
750
|
+
end
|
|
751
|
+
|
|
752
|
+
self
|
|
753
|
+
end
|
|
754
|
+
|
|
556
755
|
# Update multiple attributes at once, saving the object once the updates
|
|
557
756
|
# are complete.
|
|
558
757
|
#
|
|
@@ -564,6 +763,10 @@ module Dynamoid
|
|
|
564
763
|
# Raises a +Dynamoid::Errors::UnknownAttribute+ exception if any of the
|
|
565
764
|
# attributes is not on the model
|
|
566
765
|
#
|
|
766
|
+
# Raises +Dynamoid::Errors::MissingHashKey+ if a partition key has value
|
|
767
|
+
# +nil+ and raises +Dynamoid::Errors::MissingRangeKey+ if a sort key is
|
|
768
|
+
# required but has value +nil+.
|
|
769
|
+
#
|
|
567
770
|
# @param attributes [Hash] a hash of attributes to update
|
|
568
771
|
# @return [true|false] Whether updating successful or not
|
|
569
772
|
# @since 0.2.0
|
|
@@ -580,9 +783,17 @@ module Dynamoid
|
|
|
580
783
|
# Raises a +Dynamoid::Errors::DocumentNotValid+ exception if some vaidation
|
|
581
784
|
# fails.
|
|
582
785
|
#
|
|
786
|
+
# If any of the +before_*+ callbacks throws +:abort+ the updating is
|
|
787
|
+
# cancelled and +update_attributes!+ raises
|
|
788
|
+
# +Dynamoid::Errors::RecordNotSaved+.
|
|
789
|
+
#
|
|
583
790
|
# Raises a +Dynamoid::Errors::UnknownAttribute+ exception if any of the
|
|
584
791
|
# attributes is not on the model
|
|
585
792
|
#
|
|
793
|
+
# Raises +Dynamoid::Errors::MissingHashKey+ if a partition key has value
|
|
794
|
+
# +nil+ and raises +Dynamoid::Errors::MissingRangeKey+ if a sort key is
|
|
795
|
+
# required but has value +nil+.
|
|
796
|
+
#
|
|
586
797
|
# @param attributes [Hash] a hash of attributes to update
|
|
587
798
|
def update_attributes!(attributes)
|
|
588
799
|
attributes.each { |attribute, value| write_attribute(attribute, value) }
|
|
@@ -591,8 +802,6 @@ module Dynamoid
|
|
|
591
802
|
|
|
592
803
|
# Update a single attribute, saving the object afterwards.
|
|
593
804
|
#
|
|
594
|
-
# Returns +true+ if saving is successful and +false+ otherwise.
|
|
595
|
-
#
|
|
596
805
|
# user.update_attribute(:last_name, 'Tylor')
|
|
597
806
|
#
|
|
598
807
|
# Validation is skipped.
|
|
@@ -607,9 +816,28 @@ module Dynamoid
|
|
|
607
816
|
# @since 0.2.0
|
|
608
817
|
def update_attribute(attribute, value)
|
|
609
818
|
# final implementation is in the Dynamoid::Validation module
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
819
|
+
end
|
|
820
|
+
|
|
821
|
+
# Update a single attribute, saving the object afterwards.
|
|
822
|
+
#
|
|
823
|
+
# user.update_attribute!(:last_name, 'Tylor')
|
|
824
|
+
#
|
|
825
|
+
# Validation is skipped.
|
|
826
|
+
#
|
|
827
|
+
# If any of the +before_*+ callbacks throws +:abort+ the updating is
|
|
828
|
+
# cancelled and +update_attribute!+ raises
|
|
829
|
+
# +Dynamoid::Errors::RecordNotSaved+.
|
|
830
|
+
#
|
|
831
|
+
# Raises a +Dynamoid::Errors::UnknownAttribute+ exception if any of the
|
|
832
|
+
# attributes is not on the model
|
|
833
|
+
#
|
|
834
|
+
# @param attribute [Symbol] attribute name to update
|
|
835
|
+
# @param value [Object] the value to assign it
|
|
836
|
+
# @return [Dynamoid::Document] self
|
|
837
|
+
#
|
|
838
|
+
# @since 0.2.0
|
|
839
|
+
def update_attribute!(attribute, value)
|
|
840
|
+
# final implementation is in the Dynamoid::Validation module
|
|
613
841
|
end
|
|
614
842
|
|
|
615
843
|
# Update a model.
|
|
@@ -620,8 +848,7 @@ module Dynamoid
|
|
|
620
848
|
# attributes. Supports following operations: +add+, +delete+ and +set+.
|
|
621
849
|
#
|
|
622
850
|
# Operation +add+ just adds a value for numeric attributes and join
|
|
623
|
-
# collections if attribute is a
|
|
624
|
-
# +map+).
|
|
851
|
+
# collections if attribute is a set.
|
|
625
852
|
#
|
|
626
853
|
# user.update! do |t|
|
|
627
854
|
# t.add(age: 1, followers_count: 5)
|
|
@@ -646,7 +873,7 @@ module Dynamoid
|
|
|
646
873
|
# {parameter}[https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/LegacyConditionalParameters.AttributeUpdates.html]
|
|
647
874
|
# of +UpdateItem+ operation.
|
|
648
875
|
#
|
|
649
|
-
# It's
|
|
876
|
+
# It's atomic operations. So adding or deleting elements in a collection
|
|
650
877
|
# or incrementing or decrementing a numeric field is atomic and does not
|
|
651
878
|
# interfere with other write requests.
|
|
652
879
|
#
|
|
@@ -686,9 +913,18 @@ module Dynamoid
|
|
|
686
913
|
|
|
687
914
|
begin
|
|
688
915
|
table_name = self.class.table_name
|
|
916
|
+
partition_key_dumped = Dumping.dump_field(hash_key, self.class.attributes[self.class.hash_key])
|
|
917
|
+
conditions = conditions.dup
|
|
918
|
+
conditions[:if] ||= {}
|
|
919
|
+
conditions[:if][self.class.hash_key] = partition_key_dumped
|
|
920
|
+
if self.class.range_key
|
|
921
|
+
sort_key_dumped = Dumping.dump_field(range_value, self.class.attributes[self.class.range_key])
|
|
922
|
+
conditions[:if][self.class.range_key] = sort_key_dumped
|
|
923
|
+
end
|
|
924
|
+
|
|
689
925
|
update_item_options = options.merge(conditions: conditions)
|
|
690
926
|
|
|
691
|
-
new_attrs = Dynamoid.adapter.update_item(table_name,
|
|
927
|
+
new_attrs = Dynamoid.adapter.update_item(table_name, partition_key_dumped, update_item_options) do |t|
|
|
692
928
|
item_updater = ItemUpdaterWithDumping.new(self.class, t)
|
|
693
929
|
|
|
694
930
|
item_updater.add(lock_version: 1) if self.class.attributes[:lock_version]
|
|
@@ -701,6 +937,9 @@ module Dynamoid
|
|
|
701
937
|
end
|
|
702
938
|
load(Undumping.undump_attributes(new_attrs, self.class.attributes))
|
|
703
939
|
rescue Dynamoid::Errors::ConditionalCheckFailedException
|
|
940
|
+
# exception may be raised either because of failed user provided conditions
|
|
941
|
+
# or because of conditions on partition and sort keys. We cannot
|
|
942
|
+
# distinguish these two cases.
|
|
704
943
|
raise Dynamoid::Errors::StaleObjectError.new(self, 'update')
|
|
705
944
|
end
|
|
706
945
|
end
|
|
@@ -716,8 +955,7 @@ module Dynamoid
|
|
|
716
955
|
# attributes. Supports following operations: +add+, +delete+ and +set+.
|
|
717
956
|
#
|
|
718
957
|
# Operation +add+ just adds a value for numeric attributes and join
|
|
719
|
-
# collections if attribute is a
|
|
720
|
-
# +map+).
|
|
958
|
+
# collections if attribute is a set.
|
|
721
959
|
#
|
|
722
960
|
# user.update do |t|
|
|
723
961
|
# t.add(age: 1, followers_count: 5)
|
|
@@ -891,16 +1129,19 @@ module Dynamoid
|
|
|
891
1129
|
#
|
|
892
1130
|
# Returns +self+ if deleted successfully and +false+ otherwise.
|
|
893
1131
|
#
|
|
1132
|
+
# Raises +Dynamoid::Errors::MissingHashKey+ if a partition key has value
|
|
1133
|
+
# +nil+ and raises +Dynamoid::Errors::MissingRangeKey+ if a sort key is
|
|
1134
|
+
# required but has value +nil+.
|
|
1135
|
+
#
|
|
894
1136
|
# @return [Dynamoid::Document|false] whether deleted successfully
|
|
895
1137
|
# @since 0.2.0
|
|
896
1138
|
def destroy
|
|
897
|
-
|
|
1139
|
+
run_callbacks(:destroy) do
|
|
898
1140
|
delete
|
|
1141
|
+
@destroyed = true
|
|
899
1142
|
end
|
|
900
1143
|
|
|
901
|
-
@destroyed
|
|
902
|
-
|
|
903
|
-
ret == false ? false : self
|
|
1144
|
+
@destroyed ? self : false
|
|
904
1145
|
end
|
|
905
1146
|
|
|
906
1147
|
# Delete a model.
|
|
@@ -912,6 +1153,10 @@ module Dynamoid
|
|
|
912
1153
|
#
|
|
913
1154
|
# Raises +Dynamoid::Errors::RecordNotDestroyed+ exception if model deleting
|
|
914
1155
|
# failed.
|
|
1156
|
+
#
|
|
1157
|
+
# Raises +Dynamoid::Errors::MissingHashKey+ if a partition key has value
|
|
1158
|
+
# +nil+ and raises +Dynamoid::Errors::MissingRangeKey+ if a sort key is
|
|
1159
|
+
# required but has value +nil+.
|
|
915
1160
|
def destroy!
|
|
916
1161
|
destroy || (raise Dynamoid::Errors::RecordNotDestroyed, self)
|
|
917
1162
|
end
|
|
@@ -924,10 +1169,18 @@ module Dynamoid
|
|
|
924
1169
|
# Raises +Dynamoid::Errors::StaleObjectError+ exception if cannot delete a
|
|
925
1170
|
# model.
|
|
926
1171
|
#
|
|
1172
|
+
# Raises +Dynamoid::Errors::MissingHashKey+ if a partition key has value
|
|
1173
|
+
# +nil+ and raises +Dynamoid::Errors::MissingRangeKey+ if a sort key is
|
|
1174
|
+
# required but has value +nil+.
|
|
1175
|
+
#
|
|
927
1176
|
# @return [Dynamoid::Document] self
|
|
928
1177
|
# @since 0.2.0
|
|
929
1178
|
def delete
|
|
1179
|
+
raise Dynamoid::Errors::MissingHashKey if hash_key.nil?
|
|
1180
|
+
raise Dynamoid::Errors::MissingRangeKey if self.class.range_key? && range_value.nil?
|
|
1181
|
+
|
|
930
1182
|
options = range_key ? { range_key: Dumping.dump_field(read_attribute(range_key), self.class.attributes[range_key]) } : {}
|
|
1183
|
+
partition_key_dumped = Dumping.dump_field(hash_key, self.class.attributes[self.class.hash_key])
|
|
931
1184
|
|
|
932
1185
|
# Add an optimistic locking check if the lock_version column exists
|
|
933
1186
|
if self.class.attributes[:lock_version]
|
|
@@ -943,7 +1196,7 @@ module Dynamoid
|
|
|
943
1196
|
|
|
944
1197
|
@destroyed = true
|
|
945
1198
|
|
|
946
|
-
Dynamoid.adapter.delete(self.class.table_name,
|
|
1199
|
+
Dynamoid.adapter.delete(self.class.table_name, partition_key_dumped, options)
|
|
947
1200
|
|
|
948
1201
|
self.class.associations.each_key do |name|
|
|
949
1202
|
send(name).disassociate_source
|
|
@@ -951,6 +1204,7 @@ module Dynamoid
|
|
|
951
1204
|
|
|
952
1205
|
self
|
|
953
1206
|
rescue Dynamoid::Errors::ConditionalCheckFailedException
|
|
1207
|
+
@destroyed = false
|
|
954
1208
|
raise Dynamoid::Errors::StaleObjectError.new(self, 'delete')
|
|
955
1209
|
end
|
|
956
1210
|
end
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dynamoid
|
|
4
|
+
class TransactionRead
|
|
5
|
+
class Find
|
|
6
|
+
attr_reader :model_class
|
|
7
|
+
|
|
8
|
+
def initialize(model_class, *ids, **options)
|
|
9
|
+
@model_class = model_class
|
|
10
|
+
@ids = ids
|
|
11
|
+
@options = options
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def on_registration
|
|
15
|
+
validate_primary_key!
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def observable_by_user_result
|
|
19
|
+
nil
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def action_request
|
|
23
|
+
if single_key_given?
|
|
24
|
+
action_request_for_single_key
|
|
25
|
+
else
|
|
26
|
+
action_request_for_multiple_keys
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def process_responses(responses)
|
|
31
|
+
models = responses.map do |response|
|
|
32
|
+
if response.item
|
|
33
|
+
@model_class.from_database(response.item)
|
|
34
|
+
elsif @options[:raise_error] == false
|
|
35
|
+
nil
|
|
36
|
+
else
|
|
37
|
+
message = build_record_not_found_exception_message(responses)
|
|
38
|
+
raise Dynamoid::Errors::RecordNotFound, message
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
unless single_key_given?
|
|
43
|
+
models.compact!
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
models.each { |m| m&.run_callbacks :find }
|
|
47
|
+
models
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def single_key_given?
|
|
53
|
+
@ids.size == 1 && !@ids[0].is_a?(Array)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def validate_primary_key!
|
|
57
|
+
if single_key_given?
|
|
58
|
+
partition_key = @ids[0]
|
|
59
|
+
sort_key = @options[:range_key]
|
|
60
|
+
|
|
61
|
+
raise Dynamoid::Errors::MissingHashKey if partition_key.nil?
|
|
62
|
+
raise Dynamoid::Errors::MissingRangeKey if @model_class.range_key && sort_key.nil?
|
|
63
|
+
else
|
|
64
|
+
ids = @ids.flatten(1)
|
|
65
|
+
|
|
66
|
+
raise Dynamoid::Errors::MissingHashKey if ids.any? { |pk, _sk| pk.nil? }
|
|
67
|
+
raise Errors::MissingRangeKey if @model_class.range_key && ids.any? { |_pk, sk| sk.nil? }
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def action_request_for_single_key
|
|
72
|
+
partition_key = @ids[0]
|
|
73
|
+
|
|
74
|
+
key = { @model_class.hash_key => cast_and_dump_attribute(@model_class.hash_key, partition_key) }
|
|
75
|
+
|
|
76
|
+
if @model_class.range_key
|
|
77
|
+
sort_key = @options[:range_key]
|
|
78
|
+
key[@model_class.range_key] = cast_and_dump_attribute(@model_class.range_key, sort_key)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
{
|
|
82
|
+
get: {
|
|
83
|
+
key: key,
|
|
84
|
+
table_name: @model_class.table_name
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def action_request_for_multiple_keys
|
|
90
|
+
@ids.flatten(1).map do |id|
|
|
91
|
+
if @model_class.range_key
|
|
92
|
+
# expect [hash-key, range-key] pair
|
|
93
|
+
pk, sk = id
|
|
94
|
+
pk_dumped = cast_and_dump_attribute(@model_class.hash_key, pk)
|
|
95
|
+
sk_dumped = cast_and_dump_attribute(@model_class.range_key, sk)
|
|
96
|
+
|
|
97
|
+
key = { @model_class.hash_key => pk_dumped, @model_class.range_key => sk_dumped }
|
|
98
|
+
else
|
|
99
|
+
pk_dumped = cast_and_dump_attribute(@model_class.hash_key, id)
|
|
100
|
+
|
|
101
|
+
key = { @model_class.hash_key => pk_dumped }
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
{
|
|
105
|
+
get: {
|
|
106
|
+
key: key,
|
|
107
|
+
table_name: @model_class.table_name
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def cast_and_dump_attribute(name, value)
|
|
114
|
+
attribute_options = @model_class.attributes[name]
|
|
115
|
+
casted_value = TypeCasting.cast_field(value, attribute_options)
|
|
116
|
+
Dumping.dump_field(casted_value, attribute_options)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def build_record_not_found_exception_message(responses)
|
|
120
|
+
items = responses.map(&:item)
|
|
121
|
+
ids = @ids.flatten(1)
|
|
122
|
+
|
|
123
|
+
if single_key_given?
|
|
124
|
+
id = ids[0]
|
|
125
|
+
primary_key = @model_class.range_key ? "(#{id.inspect},#{@options[:range_key].inspect})" : id.inspect
|
|
126
|
+
message = "Couldn't find #{@model_class.name} with primary key #{primary_key}"
|
|
127
|
+
else
|
|
128
|
+
ids_list = @model_class.range_key ? ids.map { |pk, sk| "(#{pk.inspect},#{sk.inspect})" } : ids.map(&:inspect)
|
|
129
|
+
message = "Couldn't find all #{@model_class.name.pluralize} with primary keys [#{ids_list.join(', ')}] "
|
|
130
|
+
message += "(found #{items.compact.size} results, but was looking for #{items.size})"
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
message
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|