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
@@ -1,46 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'base'
4
- require 'dynamoid/persistence/update_validations'
5
-
6
- module Dynamoid
7
- class TransactionWrite
8
- class UpdateAttributes < Base
9
- def initialize(model, attributes, **options)
10
- super()
11
-
12
- @model = model
13
- @model.assign_attributes(attributes)
14
- @save_action = Save.new(model, **options)
15
- end
16
-
17
- def on_registration
18
- @save_action.on_registration
19
- end
20
-
21
- def on_commit
22
- @save_action.on_commit
23
- end
24
-
25
- def on_rollback
26
- @save_action.on_rollback
27
- end
28
-
29
- def aborted?
30
- @save_action.aborted?
31
- end
32
-
33
- def skipped?
34
- @save_action.skipped?
35
- end
36
-
37
- def observable_by_user_result
38
- @save_action.observable_by_user_result
39
- end
40
-
41
- def action_request
42
- @save_action.action_request
43
- end
44
- end
45
- end
46
- end
@@ -1,247 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'base'
4
- require 'dynamoid/persistence/update_validations'
5
-
6
- module Dynamoid
7
- class TransactionWrite
8
- class UpdateFields < Base
9
- def initialize(model_class, hash_key, range_key, attributes, &block)
10
- super()
11
-
12
- @model_class = model_class
13
- @hash_key = hash_key
14
- @range_key = range_key
15
- @attributes = attributes || {}
16
- @block = block
17
- end
18
-
19
- def on_registration
20
- validate_primary_key!
21
- Dynamoid::Persistence::UpdateValidations.validate_attributes_exist(@model_class, @attributes)
22
-
23
- if @block
24
- @item_updater = ItemUpdater.new(@model_class)
25
- @block.call(@item_updater)
26
- end
27
- end
28
-
29
- def on_commit; end
30
-
31
- def on_rollback; end
32
-
33
- def aborted?
34
- false
35
- end
36
-
37
- def skipped?
38
- @attributes.empty? && (!@item_updater || @item_updater.empty?)
39
- end
40
-
41
- def observable_by_user_result
42
- nil
43
- end
44
-
45
- def action_request
46
- builder = UpdateRequestBuilder.new(@model_class)
47
-
48
- # primary key to look up an item to update
49
- builder.hash_key = dump_attribute(@model_class.hash_key, @hash_key)
50
- builder.range_key = dump_attribute(@model_class.range_key, @range_key) if @model_class.range_key?
51
-
52
- # changed attributes to persist
53
- changes = @attributes.dup
54
- changes = add_timestamps(changes, skip_created_at: true)
55
- changes_dumped = Dynamoid::Dumping.dump_attributes(changes, @model_class.attributes)
56
-
57
- if Dynamoid.config.store_attribute_with_nil_value
58
- builder.set_attributes(changes_dumped)
59
- else
60
- nil_attributes = changes_dumped.select { |_, v| v.nil? }
61
- non_nil_attributes = changes_dumped.reject { |_, v| v.nil? } # rubocop:disable Style/PartitionInsteadOfDoubleSelect
62
-
63
- builder.remove_attributes(nil_attributes.keys)
64
- builder.set_attributes(non_nil_attributes)
65
- end
66
-
67
- # given a block
68
- if @item_updater
69
- builder.set_attributes(@item_updater.attributes_to_set)
70
- builder.remove_attributes(@item_updater.attributes_to_remove)
71
-
72
- @item_updater.attributes_to_add.each do |name, value|
73
- # The ADD section in UpdateExpressions requires values to be a
74
- # set to update a set attribute.
75
- # Allow specifying values as any Enumerable collection (e.g. Array).
76
- # Allow a single value not wrapped into a Set
77
- if @model_class.attributes[name][:type] == :set
78
- value = value.is_a?(Enumerable) ? Set.new(value) : Set[value]
79
- end
80
-
81
- builder.add_value(name, value)
82
- end
83
-
84
- @item_updater.attributes_to_delete.each do |name, value|
85
- # The DELETE section in UpdateExpressions requires values to be a
86
- # set to update a set attribute.
87
- # Allow specifying values as any Enumerable collection (e.g. Array).
88
- # Allow a single value not wrapped into a Set
89
- value = value.is_a?(Enumerable) ? Set.new(value) : Set[value]
90
-
91
- builder.delete_value(name, value)
92
- end
93
- end
94
-
95
- # require primary key to exist
96
- condition_expression = "attribute_exists(#{@model_class.hash_key})"
97
- if @model_class.range_key?
98
- condition_expression += " AND attribute_exists(#{@model_class.range_key})"
99
- end
100
- builder.condition_expression = condition_expression
101
-
102
- builder.request
103
- end
104
-
105
- private
106
-
107
- def validate_primary_key!
108
- raise Dynamoid::Errors::MissingHashKey if @hash_key.nil?
109
- raise Dynamoid::Errors::MissingRangeKey if @model_class.range_key? && @range_key.nil?
110
- end
111
-
112
- def add_timestamps(attributes, skip_created_at: false)
113
- return attributes unless @model_class.timestamps_enabled?
114
-
115
- result = attributes.clone
116
- timestamp = DateTime.now.in_time_zone(Time.zone)
117
- result[:created_at] ||= timestamp unless skip_created_at
118
- result[:updated_at] ||= timestamp
119
- result
120
- end
121
-
122
- def dump_attribute(name, value)
123
- options = @model_class.attributes[name]
124
- Dumping.dump_field(value, options)
125
- end
126
-
127
- class UpdateRequestBuilder
128
- attr_writer :hash_key, :range_key, :condition_expression
129
-
130
- def initialize(model_class)
131
- @model_class = model_class
132
-
133
- @attributes_to_set = {}
134
- @attributes_to_add = {}
135
- @attributes_to_delete = {}
136
- @attributes_to_remove = []
137
- @condition_expression = nil
138
- end
139
-
140
- def set_attributes(attributes) # rubocop:disable Naming/AccessorMethodName
141
- @attributes_to_set.merge!(attributes)
142
- end
143
-
144
- def add_value(name, value)
145
- @attributes_to_add[name] = value
146
- end
147
-
148
- def delete_value(name, value)
149
- @attributes_to_delete[name] = value
150
- end
151
-
152
- def remove_attributes(names)
153
- @attributes_to_remove.concat(names)
154
- end
155
-
156
- def request
157
- key = { @model_class.hash_key => @hash_key }
158
- key[@model_class.range_key] = @range_key if @model_class.range_key?
159
-
160
- # Build UpdateExpression and keep names and values placeholders mapping
161
- # in ExpressionAttributeNames and ExpressionAttributeValues.
162
- update_expression_statements = []
163
- expression_attribute_names = {}
164
- expression_attribute_values = {}
165
- name_placeholder = '#_n0'
166
- value_placeholder = ':_v0'
167
-
168
- unless @attributes_to_set.empty?
169
- statements = []
170
-
171
- @attributes_to_set.each do |name, value|
172
- statements << "#{name_placeholder} = #{value_placeholder}"
173
-
174
- expression_attribute_names[name_placeholder] = name
175
- expression_attribute_values[value_placeholder] = value
176
-
177
- name_placeholder = name_placeholder.succ
178
- value_placeholder = value_placeholder.succ
179
- end
180
-
181
- update_expression_statements << "SET #{statements.join(', ')}"
182
- end
183
-
184
- unless @attributes_to_add.empty?
185
- statements = []
186
-
187
- @attributes_to_add.each do |name, value|
188
- statements << "#{name_placeholder} #{value_placeholder}"
189
-
190
- expression_attribute_names[name_placeholder] = name
191
- expression_attribute_values[value_placeholder] = value
192
-
193
- name_placeholder = name_placeholder.succ
194
- value_placeholder = value_placeholder.succ
195
- end
196
-
197
- update_expression_statements << "ADD #{statements.join(', ')}"
198
- end
199
-
200
- unless @attributes_to_delete.empty?
201
- statements = []
202
-
203
- @attributes_to_delete.each do |name, value|
204
- statements << "#{name_placeholder} #{value_placeholder}"
205
-
206
- expression_attribute_names[name_placeholder] = name
207
- expression_attribute_values[value_placeholder] = value
208
-
209
- name_placeholder = name_placeholder.succ
210
- value_placeholder = value_placeholder.succ
211
- end
212
-
213
- update_expression_statements << "DELETE #{statements.join(', ')}"
214
- end
215
-
216
- unless @attributes_to_remove.empty?
217
- name_placeholders = []
218
-
219
- @attributes_to_remove.each do |name|
220
- name_placeholders << name_placeholder
221
-
222
- expression_attribute_names[name_placeholder] = name
223
-
224
- name_placeholder = name_placeholder.succ
225
- value_placeholder = value_placeholder.succ
226
- end
227
-
228
- update_expression_statements << "REMOVE #{name_placeholders.join(', ')}"
229
- end
230
-
231
- update_expression = update_expression_statements.join(' ')
232
-
233
- {
234
- update: {
235
- key: key,
236
- table_name: @model_class.table_name,
237
- update_expression: update_expression,
238
- expression_attribute_names: expression_attribute_names,
239
- expression_attribute_values: expression_attribute_values,
240
- condition_expression: @condition_expression
241
- }
242
- }
243
- end
244
- end
245
- end
246
- end
247
- end
@@ -1,113 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'base'
4
- require 'dynamoid/persistence/update_validations'
5
-
6
- module Dynamoid
7
- class TransactionWrite
8
- class Upsert < Base
9
- def initialize(model_class, hash_key, range_key, attributes)
10
- super()
11
-
12
- @model_class = model_class
13
- @hash_key = hash_key
14
- @range_key = range_key
15
- @attributes = attributes
16
- end
17
-
18
- def on_registration
19
- validate_primary_key!
20
- Dynamoid::Persistence::UpdateValidations.validate_attributes_exist(@model_class, @attributes)
21
- end
22
-
23
- def on_commit; end
24
-
25
- def on_rollback; end
26
-
27
- def aborted?
28
- false
29
- end
30
-
31
- def skipped?
32
- attributes_to_assign = @attributes.except(@model_class.hash_key, @model_class.range_key)
33
- attributes_to_assign.empty? && !@model_class.timestamps_enabled?
34
- end
35
-
36
- def observable_by_user_result
37
- nil
38
- end
39
-
40
- def action_request
41
- # changed attributes to persist
42
- changes = @attributes.dup
43
- changes = add_timestamps(changes, skip_created_at: true)
44
- changes_dumped = Dynamoid::Dumping.dump_attributes(changes, @model_class.attributes)
45
-
46
- # primary key to look up an item to update
47
- partition_key_dumped = dump(@model_class.hash_key, @hash_key)
48
- key = { @model_class.hash_key => partition_key_dumped }
49
-
50
- if @model_class.range_key?
51
- sort_key_dumped = dump(@model_class.range_key, @range_key)
52
- key[@model_class.range_key] = sort_key_dumped
53
- end
54
-
55
- # Build UpdateExpression and keep names and values placeholders mapping
56
- # in ExpressionAttributeNames and ExpressionAttributeValues.
57
- set_expression_statements = []
58
- remove_expression_statements = []
59
- expression_attribute_names = {}
60
- expression_attribute_values = {}
61
-
62
- changes_dumped.each_with_index do |(name, value), i|
63
- name_placeholder = "#_n#{i}"
64
- value_placeholder = ":_s#{i}"
65
-
66
- if value || Dynamoid.config.store_attribute_with_nil_value
67
- set_expression_statements << "#{name_placeholder} = #{value_placeholder}"
68
- expression_attribute_values[value_placeholder] = value
69
- else
70
- remove_expression_statements << name_placeholder
71
- end
72
- expression_attribute_names[name_placeholder] = name
73
- end
74
-
75
- update_expression = ''
76
- update_expression += "SET #{set_expression_statements.join(', ')}" if set_expression_statements.any?
77
- update_expression += " REMOVE #{remove_expression_statements.join(', ')}" if remove_expression_statements.any?
78
-
79
- {
80
- update: {
81
- key: key,
82
- table_name: @model_class.table_name,
83
- update_expression: update_expression,
84
- expression_attribute_names: expression_attribute_names,
85
- expression_attribute_values: expression_attribute_values
86
- }
87
- }
88
- end
89
-
90
- private
91
-
92
- def validate_primary_key!
93
- raise Dynamoid::Errors::MissingHashKey if @hash_key.nil?
94
- raise Dynamoid::Errors::MissingRangeKey if @model_class.range_key? && @range_key.nil?
95
- end
96
-
97
- def add_timestamps(attributes, skip_created_at: false)
98
- return attributes unless @model_class.timestamps_enabled?
99
-
100
- result = attributes.clone
101
- timestamp = DateTime.now.in_time_zone(Time.zone)
102
- result[:created_at] ||= timestamp unless skip_created_at
103
- result[:updated_at] ||= timestamp
104
- result
105
- end
106
-
107
- def dump(name, value)
108
- options = @model_class.attributes[name]
109
- Dumping.dump_field(value, options)
110
- end
111
- end
112
- end
113
- end