dynamoid 3.13.0 → 3.14.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.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +41 -4
  3. data/README.md +60 -1584
  4. data/lib/dynamoid/associations/association.rb +10 -2
  5. data/lib/dynamoid/associations/belongs_to.rb +3 -1
  6. data/lib/dynamoid/associations/has_and_belongs_to_many.rb +0 -1
  7. data/lib/dynamoid/associations/has_many.rb +0 -1
  8. data/lib/dynamoid/associations/has_one.rb +1 -2
  9. data/lib/dynamoid/associations/many_association.rb +1 -0
  10. data/lib/dynamoid/associations.rb +64 -48
  11. data/lib/dynamoid/components.rb +1 -0
  12. data/lib/dynamoid/criteria/chain.rb +43 -30
  13. data/lib/dynamoid/dirty.rb +13 -3
  14. data/lib/dynamoid/document.rb +29 -21
  15. data/lib/dynamoid/fields.rb +55 -41
  16. data/lib/dynamoid/finders.rb +1 -1
  17. data/lib/dynamoid/indexes.rb +29 -21
  18. data/lib/dynamoid/persistence/inc.rb +15 -7
  19. data/lib/dynamoid/persistence/save.rb +21 -14
  20. data/lib/dynamoid/persistence.rb +88 -54
  21. data/lib/dynamoid/transactions/mutation/base.rb +76 -0
  22. data/lib/dynamoid/transactions/mutation/builders/delete_request_builder.rb +44 -0
  23. data/lib/dynamoid/transactions/mutation/builders/update_request_builder.rb +139 -0
  24. data/lib/dynamoid/transactions/mutation/create.rb +52 -0
  25. data/lib/dynamoid/transactions/mutation/delete_with_instance.rb +80 -0
  26. data/lib/dynamoid/transactions/mutation/delete_with_primary_key.rb +62 -0
  27. data/lib/dynamoid/transactions/mutation/destroy.rb +96 -0
  28. data/lib/dynamoid/transactions/mutation/import.rb +84 -0
  29. data/lib/dynamoid/transactions/mutation/inc.rb +101 -0
  30. data/lib/dynamoid/transactions/mutation/increment.rb +63 -0
  31. data/lib/dynamoid/transactions/mutation/save.rb +194 -0
  32. data/lib/dynamoid/transactions/mutation/touch.rb +99 -0
  33. data/lib/dynamoid/transactions/mutation/update_attributes.rb +49 -0
  34. data/lib/dynamoid/transactions/mutation/update_fields.rb +185 -0
  35. data/lib/dynamoid/transactions/mutation/upsert.rb +94 -0
  36. data/lib/dynamoid/transactions/mutation.rb +935 -0
  37. data/lib/dynamoid/transactions/retrieval/find.rb +140 -0
  38. data/lib/dynamoid/transactions/retrieval.rb +150 -0
  39. data/lib/dynamoid/transactions.rb +63 -0
  40. data/lib/dynamoid/version.rb +1 -1
  41. data/lib/dynamoid.rb +1 -2
  42. metadata +23 -17
  43. data/lib/dynamoid/transaction_read/find.rb +0 -137
  44. data/lib/dynamoid/transaction_read.rb +0 -146
  45. data/lib/dynamoid/transaction_write/base.rb +0 -59
  46. data/lib/dynamoid/transaction_write/create.rb +0 -49
  47. data/lib/dynamoid/transaction_write/delete_with_instance.rb +0 -65
  48. data/lib/dynamoid/transaction_write/delete_with_primary_key.rb +0 -64
  49. data/lib/dynamoid/transaction_write/destroy.rb +0 -84
  50. data/lib/dynamoid/transaction_write/item_updater.rb +0 -60
  51. data/lib/dynamoid/transaction_write/save.rb +0 -177
  52. data/lib/dynamoid/transaction_write/update_attributes.rb +0 -46
  53. data/lib/dynamoid/transaction_write/update_fields.rb +0 -247
  54. data/lib/dynamoid/transaction_write/upsert.rb +0 -113
  55. data/lib/dynamoid/transaction_write.rb +0 -673
@@ -6,9 +6,10 @@ module Dynamoid
6
6
  # The target is the object which is referencing by this association.
7
7
  # @private
8
8
  module Associations
9
- # @private
10
9
  module Association
11
- attr_accessor :name, :options, :source, :loaded
10
+ attr_accessor :name, :options, :source
11
+ # @private
12
+ attr_accessor :loaded
12
13
 
13
14
  # Create a new association.
14
15
  #
@@ -30,12 +31,15 @@ module Dynamoid
30
31
  @loaded = false
31
32
  end
32
33
 
34
+ # @private
33
35
  def loaded?
34
36
  @loaded
35
37
  end
36
38
 
39
+ # @private
37
40
  def find_target; end
38
41
 
42
+ # @private
39
43
  def target
40
44
  unless loaded?
41
45
  @target = find_target
@@ -45,19 +49,23 @@ module Dynamoid
45
49
  @target
46
50
  end
47
51
 
52
+ # @private
48
53
  def reset
49
54
  @target = nil
50
55
  @loaded = false
51
56
  end
52
57
 
58
+ # @private
53
59
  def declaration_field_name
54
60
  "#{name}_ids"
55
61
  end
56
62
 
63
+ # @private
57
64
  def declaration_field_type
58
65
  :set
59
66
  end
60
67
 
68
+ # @private
61
69
  def disassociate_source
62
70
  Array(target).each do |target_entry|
63
71
  target_entry.send(target_association).disassociate(source.hash_key) if target_entry && target_association
@@ -4,14 +4,15 @@ module Dynamoid
4
4
  # The belongs_to association. For belongs_to, we reference only a single target instead of multiple records; that target is the
5
5
  # object to which the association object is associated.
6
6
  module Associations
7
- # @private
8
7
  class BelongsTo
9
8
  include SingleAssociation
10
9
 
10
+ # @private
11
11
  def declaration_field_name
12
12
  options[:foreign_key] || "#{name}_ids"
13
13
  end
14
14
 
15
+ # @private
15
16
  def declaration_field_type
16
17
  if options[:foreign_key]
17
18
  target_class.attributes[target_class.hash_key][:type]
@@ -20,6 +21,7 @@ module Dynamoid
20
21
  end
21
22
  end
22
23
 
24
+ # @private
23
25
  # Override default implementation
24
26
  # to handle case when we store id as scalar value, not as collection
25
27
  def associate(hash_key)
@@ -3,7 +3,6 @@
3
3
  module Dynamoid
4
4
  # The has and belongs to many association.
5
5
  module Associations
6
- # @private
7
6
  class HasAndBelongsToMany
8
7
  include ManyAssociation
9
8
 
@@ -3,7 +3,6 @@
3
3
  module Dynamoid
4
4
  # The has_many association.
5
5
  module Associations
6
- # @private
7
6
  class HasMany
8
7
  include ManyAssociation
9
8
 
@@ -1,9 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Dynamoid
4
- # The HasOne association.
5
4
  module Associations
6
- # @private
5
+ # The HasOne association.
7
6
  class HasOne
8
7
  include Association
9
8
  include SingleAssociation
@@ -5,6 +5,7 @@ module Dynamoid
5
5
  module ManyAssociation
6
6
  include Association
7
7
 
8
+ # @private
8
9
  attr_accessor :query
9
10
 
10
11
  def initialize(*args)
@@ -27,11 +27,13 @@ module Dynamoid
27
27
  module ClassMethods
28
28
  # Declare a +has_many+ association for this document.
29
29
  #
30
- # class Category
31
- # include Dynamoid::Document
30
+ # ```
31
+ # class Category
32
+ # include Dynamoid::Document
32
33
  #
33
- # has_many :posts
34
- # end
34
+ # has_many :posts
35
+ # end
36
+ # ```
35
37
  #
36
38
  # Association is an enumerable collection and supports following addition
37
39
  # operations:
@@ -58,17 +60,19 @@ module Dynamoid
58
60
  # the current class and the name doesn't match a name of the current
59
61
  # class this name can be specified with +inverse_of+ option:
60
62
  #
61
- # class Post
62
- # include Dynamoid::Document
63
+ # ```
64
+ # class Post
65
+ # include Dynamoid::Document
63
66
  #
64
- # belongs_to :item, class_name: 'Tag'
65
- # end
67
+ # belongs_to :item, class_name: 'Tag'
68
+ # end
66
69
  #
67
- # class Tag
68
- # include Dynamoid::Document
70
+ # class Tag
71
+ # include Dynamoid::Document
69
72
  #
70
- # has_many :posts, inverse_of: :item
71
- # end
73
+ # has_many :posts, inverse_of: :item
74
+ # end
75
+ # ```
72
76
  #
73
77
  # @param name [Symbol] the name of the association
74
78
  # @param options [Hash] options to pass to the association constructor
@@ -83,11 +87,13 @@ module Dynamoid
83
87
 
84
88
  # Declare a +has_one+ association for this document.
85
89
  #
86
- # class Image
87
- # include Dynamoid::Document
90
+ # ```
91
+ # class Image
92
+ # include Dynamoid::Document
88
93
  #
89
- # has_one :post
90
- # end
94
+ # has_one :post
95
+ # end
96
+ # ```
91
97
  #
92
98
  # Association supports following operations:
93
99
  #
@@ -106,17 +112,19 @@ module Dynamoid
106
112
  # class and the name doesn't match a name of the current class this name
107
113
  # can be specified with +inverse_of+ option:
108
114
  #
109
- # class Post
110
- # include Dynamoid::Document
115
+ # ```
116
+ # class Post
117
+ # include Dynamoid::Document
111
118
  #
112
- # belongs_to :logo, class_name: 'Image'
113
- # end
119
+ # belongs_to :logo, class_name: 'Image'
120
+ # end
114
121
  #
115
- # class Image
116
- # include Dynamoid::Document
122
+ # class Image
123
+ # include Dynamoid::Document
117
124
  #
118
- # has_one :post, inverse_of: :logo
119
- # end
125
+ # has_one :post, inverse_of: :logo
126
+ # end
127
+ # ```
120
128
  #
121
129
  # @param name [Symbol] the name of the association
122
130
  # @param options [Hash] options to pass to the association constructor
@@ -131,11 +139,13 @@ module Dynamoid
131
139
 
132
140
  # Declare a +belongs_to+ association for this document.
133
141
  #
134
- # class Post
135
- # include Dynamoid::Document
142
+ # ```
143
+ # class Post
144
+ # include Dynamoid::Document
136
145
  #
137
- # belongs_to :categories
138
- # end
146
+ # belongs_to :categories
147
+ # end
148
+ # ```
139
149
  #
140
150
  # Association supports following operations:
141
151
  #
@@ -154,17 +164,19 @@ module Dynamoid
154
164
  # the current class and the name doesn't match a name of the current
155
165
  # class this name can be specified with +inverse_of+ option:
156
166
  #
157
- # class Category
158
- # include Dynamoid::Document
167
+ # ```
168
+ # class Category
169
+ # include Dynamoid::Document
159
170
  #
160
- # has_many :items, class_name: 'Post'
161
- # end
171
+ # has_many :items, class_name: 'Post'
172
+ # end
162
173
  #
163
- # class Post
164
- # include Dynamoid::Document
174
+ # class Post
175
+ # include Dynamoid::Document
165
176
  #
166
- # belongs_to :categories, inverse_of: :items
167
- # end
177
+ # belongs_to :categories, inverse_of: :items
178
+ # end
179
+ # ```
168
180
  #
169
181
  # By default a hash key attribute name is +id+. If an associated class
170
182
  # uses another name for a hash key attribute it should be specified in
@@ -186,11 +198,13 @@ module Dynamoid
186
198
 
187
199
  # Declare a +has_and_belongs_to_many+ association for this document.
188
200
  #
189
- # class Post
190
- # include Dynamoid::Document
201
+ # ```
202
+ # class Post
203
+ # include Dynamoid::Document
191
204
  #
192
- # has_and_belongs_to_many :tags
193
- # end
205
+ # has_and_belongs_to_many :tags
206
+ # end
207
+ # ```
194
208
  #
195
209
  # Association is an enumerable collection and supports following addition
196
210
  # operations:
@@ -217,17 +231,19 @@ module Dynamoid
217
231
  # the current class and the name doesn't match a name of the current
218
232
  # class this name can be specified with +inverse_of+ option:
219
233
  #
220
- # class Tag
221
- # include Dynamoid::Document
234
+ # ```
235
+ # class Tag
236
+ # include Dynamoid::Document
222
237
  #
223
- # has_and_belongs_to_many :items, class_name: 'Post'
224
- # end
238
+ # has_and_belongs_to_many :items, class_name: 'Post'
239
+ # end
225
240
  #
226
- # class Post
227
- # include Dynamoid::Document
241
+ # class Post
242
+ # include Dynamoid::Document
228
243
  #
229
- # has_and_belongs_to_many :tags, inverse_of: :items
230
- # end
244
+ # has_and_belongs_to_many :tags, inverse_of: :items
245
+ # end
246
+ # ```
231
247
  #
232
248
  # @param name [Symbol] the name of the association
233
249
  # @param options [Hash] options to pass to the association constructor
@@ -38,5 +38,6 @@ module Dynamoid
38
38
  include Dynamoid::Criteria
39
39
  include Dynamoid::Validations
40
40
  include Dynamoid::IdentityMap
41
+ include Dynamoid::Transactions
41
42
  end
42
43
  end
@@ -9,10 +9,12 @@ module Dynamoid
9
9
  # The criteria chain is equivalent to an ActiveRecord relation (and realistically I should change the name from
10
10
  # chain to relation). It is a chainable object that builds up a query and eventually executes it by a Query or Scan.
11
11
  class Chain
12
+ # @private
12
13
  attr_reader :source, :consistent_read, :key_fields_detector
13
14
 
14
15
  include Enumerable
15
16
 
17
+ # @private
16
18
  ALLOWED_FIELD_OPERATORS = Set.new(
17
19
  %w[
18
20
  eq ne gt lt gte lte between begins_with in contains not_contains null not_null
@@ -303,9 +305,11 @@ module Dynamoid
303
305
  # the specified size instead of relying on the default paging mechanism
304
306
  # of DynamoDB.
305
307
  #
306
- # Post.where(links_count: 2).batch(1000).all.each do |post|
307
- # # process a post
308
- # end
308
+ # ```
309
+ # Post.where(links_count: 2).batch(1000).all.each do |post|
310
+ # # process a post
311
+ # end
312
+ # ```
309
313
  #
310
314
  # It's useful to limit memory usage or throughput consumption
311
315
  #
@@ -368,19 +372,20 @@ module Dynamoid
368
372
  # needs. When this case occurs you may want to force an order. This occurs
369
373
  # when you are searching by hash key, but not specifying a range key.
370
374
  #
371
- # class Comment
372
- # include Dynamoid::Document
375
+ # ```
376
+ # class Comment
377
+ # include Dynamoid::Document
373
378
  #
374
- # table key: :post_id
375
- # range_key :author_id
379
+ # table key: :post_id
380
+ # range_key :author_id
376
381
  #
377
- # field :post_date, :datetime
382
+ # field :post_date, :datetime
378
383
  #
379
- # global_secondary_index name: :time_sorted_comments, hash_key: :post_id, range_key: post_date, projected_attributes: :all
380
- # end
384
+ # global_secondary_index name: :time_sorted_comments, hash_key: :post_id, range_key: post_date, projected_attributes: :all
385
+ # end
381
386
  #
382
- #
383
- # Comment.where(post_id: id).with_index(:time_sorted_comments).scan_index_forward(false)
387
+ # Comment.where(post_id: id).with_index(:time_sorted_comments).scan_index_forward(false)
388
+ # ```
384
389
  #
385
390
  # @return [Dynamoid::Criteria::Chain]
386
391
  def with_index(index_name)
@@ -394,14 +399,16 @@ module Dynamoid
394
399
  # Allows to use the results of a search as an enumerable over the results
395
400
  # found.
396
401
  #
397
- # Post.each do |post|
398
- # end
402
+ # ```
403
+ # Post.each do |post|
404
+ # end
399
405
  #
400
- # Post.all.each do |post|
401
- # end
406
+ # Post.all.each do |post|
407
+ # end
402
408
  #
403
- # Post.where(links_count: 2).each do |post|
404
- # end
409
+ # Post.where(links_count: 2).each do |post|
410
+ # end
411
+ # ```
405
412
  #
406
413
  # It works similar to the +all+ method so results are loaded lazily.
407
414
  #
@@ -418,9 +425,11 @@ module Dynamoid
418
425
  #
419
426
  # The pages are loaded lazily.
420
427
  #
421
- # Post.where('views_count.gt' => 1000).find_by_pages do |posts, options|
422
- # # process posts
423
- # end
428
+ # ```
429
+ # Post.where('views_count.gt' => 1000).find_by_pages do |posts, options|
430
+ # # process posts
431
+ # end
432
+ # ```
424
433
  #
425
434
  # It passes as block argument an +Array+ of models and a Hash with options.
426
435
  #
@@ -428,21 +437,25 @@ module Dynamoid
428
437
  # evaluated key is a Hash with key attributes of the last item processed by
429
438
  # DynamoDB. It can be used to resume querying using the +start+ method.
430
439
  #
431
- # posts, options = Post.where('views_count.gt' => 1000).find_by_pages.first
432
- # last_key = options[:last_evaluated_key]
440
+ # ```
441
+ # posts, options = Post.where('views_count.gt' => 1000).find_by_pages.first
442
+ # last_key = options[:last_evaluated_key]
433
443
  #
434
- # # ...
444
+ # # ...
435
445
  #
436
- # Post.where('views_count.gt' => 1000).start(last_key).find_by_pages do |posts, options|
437
- # end
446
+ # Post.where('views_count.gt' => 1000).start(last_key).find_by_pages do |posts, options|
447
+ # end
448
+ # ```
438
449
  #
439
450
  # If it's called without a block then it returns an +Enumerator+.
440
451
  #
441
- # enum = Post.where('views_count.gt' => 1000).find_by_pages
452
+ # ```
453
+ # enum = Post.where('views_count.gt' => 1000).find_by_pages
442
454
  #
443
- # enum.each do |posts, options|
444
- # # process posts
445
- # end
455
+ # enum.each do |posts, options|
456
+ # # process posts
457
+ # end
458
+ # ```
446
459
  #
447
460
  # @return [Enumerator::Lazy]
448
461
  def find_by_pages(&block)
@@ -154,7 +154,7 @@ module Dynamoid
154
154
 
155
155
  # Remove changes information for the provided attributes.
156
156
  #
157
- # @param attributes [Array[String]] - a list of attributes to clear changes for
157
+ # @param names [Array[String]] - a list of attributes to clear changes for
158
158
  def clear_attribute_changes(names)
159
159
  attributes_changed_by_setter.except!(*names)
160
160
 
@@ -195,7 +195,7 @@ module Dynamoid
195
195
 
196
196
  # Restore all previous data of the provided attributes.
197
197
  #
198
- # @param attributes [Array[Symbol]] a list of attribute names
198
+ # @param names [Array[Symbol]] a list of attribute names
199
199
  def restore_attributes(names = changed)
200
200
  names.each { |name| restore_attribute! name }
201
201
  end
@@ -303,7 +303,16 @@ module Dynamoid
303
303
  value = read_attribute(name)
304
304
  type_options = self.class.attributes[name.to_sym]
305
305
 
306
- unless type_options[:type].is_a?(Class) && !type_options[:comparable]
306
+ if self.class.attributes.dig(name.to_sym, :type) == :map && value.is_a?(Hash)
307
+ # A special case for the :map field type. attributes_from_database is a
308
+ # HashWithIndifferentAccess which has a side effect - it converts any
309
+ # nested Hash to HashWithIndifferentAccess as well.
310
+ # HashWithIndifferentAccess#== does not tolarate containing symbolized
311
+ # keys in an argument Hash. So if a value is a Hash and may have
312
+ # symbolized keys - wrap it into HashWithIndifferentAccess.
313
+ value_wrapped = value.with_indifferent_access
314
+ value_wrapped != value_from_database
315
+ elsif !type_options[:type].is_a?(Class) || type_options[:comparable]
307
316
  # common case
308
317
  value != value_from_database
309
318
  else
@@ -316,6 +325,7 @@ module Dynamoid
316
325
  end
317
326
  end
318
327
 
328
+ # @private
319
329
  module DeepDupper
320
330
  def self.dup_attributes(attributes, klass)
321
331
  attributes.map do |name, value|
@@ -89,25 +89,29 @@ module Dynamoid
89
89
  #
90
90
  # Initialize an object and pass it into a block to set other attributes.
91
91
  #
92
- # User.build(name: 'A') do |u|
93
- # u.age = 21
94
- # end
92
+ # ```
93
+ # User.build(name: 'A') do |u|
94
+ # u.age = 21
95
+ # end
96
+ # ```
95
97
  #
96
98
  # The only difference between +build+ and +new+ methods is that +build+
97
99
  # supports STI (Single table inheritance) and looks at the inheritance
98
100
  # field. So it can build a model of actual class. For instance:
99
101
  #
100
- # class Employee
101
- # include Dynamoid::Document
102
+ # ```
103
+ # class Employee
104
+ # include Dynamoid::Document
102
105
  #
103
- # field :type
104
- # field :name
105
- # end
106
+ # field :type
107
+ # field :name
108
+ # end
106
109
  #
107
- # class Manager < Employee
108
- # end
110
+ # class Manager < Employee
111
+ # end
109
112
  #
110
- # Employee.build(name: 'Alice', type: 'Manager') # => #<Manager:0x00007f945756e3f0 ...>
113
+ # Employee.build(name: 'Alice', type: 'Manager') # => #<Manager:0x00007f945756e3f0 ...>
114
+ # ```
111
115
  #
112
116
  # @param attrs [Hash] Attributes with which to create the document
113
117
  # @param block [Proc] Block to process a document after initialization
@@ -131,13 +135,15 @@ module Dynamoid
131
135
  #
132
136
  # Or in case when a range key is declared:
133
137
  #
134
- # User.exists?(
135
- # [
136
- # ['713', 'range-key-value-1'],
137
- # ['714', 'range-key-value-2'],
138
- # ['715', 'range-key-value-3']
139
- # ]
140
- # )
138
+ # ```
139
+ # User.exists?(
140
+ # [
141
+ # ['713', 'range-key-value-1'],
142
+ # ['714', 'range-key-value-2'],
143
+ # ['715', 'range-key-value-3']
144
+ # ]
145
+ # )
146
+ # ```
141
147
  #
142
148
  # It's also possible to specify models not with primary key but with
143
149
  # conditions on the attributes (in the +where+ method style):
@@ -193,9 +199,11 @@ module Dynamoid
193
199
  #
194
200
  # Initialize an object and pass it into a block to set other attributes.
195
201
  #
196
- # User.new(name: 'A') do |u|
197
- # u.age = 21
198
- # end
202
+ # ```
203
+ # User.new(name: 'A') do |u|
204
+ # u.age = 21
205
+ # end
206
+ # ```
199
207
  #
200
208
  # @param attrs [Hash] Attributes with which to create the document
201
209
  # @param block [Proc] Block to process a document after initialization