dynamoid 3.10.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +37 -1
- data/README.md +268 -8
- data/dynamoid.gemspec +4 -4
- data/lib/dynamoid/adapter.rb +1 -1
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/filter_expression_convertor.rb +53 -18
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/item_updater.rb +5 -4
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/projection_expression_convertor.rb +9 -7
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/query.rb +1 -1
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/scan.rb +1 -1
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/transact.rb +31 -0
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3.rb +17 -5
- data/lib/dynamoid/components.rb +1 -0
- data/lib/dynamoid/config.rb +3 -0
- data/lib/dynamoid/criteria/chain.rb +74 -21
- data/lib/dynamoid/criteria/where_conditions.rb +13 -6
- data/lib/dynamoid/dirty.rb +97 -11
- data/lib/dynamoid/dumping.rb +39 -17
- data/lib/dynamoid/errors.rb +30 -3
- data/lib/dynamoid/fields.rb +13 -3
- data/lib/dynamoid/finders.rb +44 -23
- data/lib/dynamoid/loadable.rb +1 -0
- data/lib/dynamoid/persistence/inc.rb +35 -19
- data/lib/dynamoid/persistence/item_updater_with_casting_and_dumping.rb +36 -0
- data/lib/dynamoid/persistence/item_updater_with_dumping.rb +33 -0
- data/lib/dynamoid/persistence/save.rb +29 -14
- data/lib/dynamoid/persistence/update_fields.rb +23 -8
- data/lib/dynamoid/persistence/update_validations.rb +3 -3
- data/lib/dynamoid/persistence/upsert.rb +22 -8
- data/lib/dynamoid/persistence.rb +184 -28
- data/lib/dynamoid/transaction_read/find.rb +137 -0
- data/lib/dynamoid/transaction_read.rb +146 -0
- data/lib/dynamoid/transaction_write/base.rb +47 -0
- data/lib/dynamoid/transaction_write/create.rb +49 -0
- data/lib/dynamoid/transaction_write/delete_with_instance.rb +65 -0
- data/lib/dynamoid/transaction_write/delete_with_primary_key.rb +64 -0
- data/lib/dynamoid/transaction_write/destroy.rb +84 -0
- data/lib/dynamoid/transaction_write/item_updater.rb +55 -0
- data/lib/dynamoid/transaction_write/save.rb +169 -0
- data/lib/dynamoid/transaction_write/update_attributes.rb +46 -0
- data/lib/dynamoid/transaction_write/update_fields.rb +239 -0
- data/lib/dynamoid/transaction_write/upsert.rb +106 -0
- data/lib/dynamoid/transaction_write.rb +673 -0
- data/lib/dynamoid/type_casting.rb +3 -1
- data/lib/dynamoid/undumping.rb +13 -2
- data/lib/dynamoid/validations.rb +8 -5
- data/lib/dynamoid/version.rb +1 -1
- data/lib/dynamoid.rb +8 -0
- metadata +21 -5
data/lib/dynamoid/finders.rb
CHANGED
@@ -14,12 +14,12 @@ module Dynamoid
|
|
14
14
|
# specified +raise_error: false+ option then +find+ will not raise the
|
15
15
|
# exception.
|
16
16
|
#
|
17
|
-
# When a document schema includes range key it always
|
17
|
+
# When a document schema includes range key it should always be specified
|
18
18
|
# in +find+ method call. In case it's missing +MissingRangeKey+ exception
|
19
19
|
# will be raised.
|
20
20
|
#
|
21
21
|
# Please note that +find+ doesn't preserve order of models in result when
|
22
|
-
#
|
22
|
+
# given multiple ids.
|
23
23
|
#
|
24
24
|
# Supported following options:
|
25
25
|
# * +consistent_read+
|
@@ -78,7 +78,7 @@ module Dynamoid
|
|
78
78
|
# # Find all the tweets using hash key and range key with consistent read
|
79
79
|
# Tweet.find_all([['1', 'red'], ['1', 'green']], consistent_read: true)
|
80
80
|
def find_all(ids, options = {})
|
81
|
-
|
81
|
+
Dynamoid.deprecator.warn('[Dynamoid] .find_all is deprecated! Call .find instead of')
|
82
82
|
|
83
83
|
_find_all(ids, options)
|
84
84
|
end
|
@@ -100,21 +100,35 @@ module Dynamoid
|
|
100
100
|
#
|
101
101
|
# @since 0.2.0
|
102
102
|
def find_by_id(id, options = {})
|
103
|
-
|
103
|
+
Dynamoid.deprecator.warn('[Dynamoid] .find_by_id is deprecated! Call .find instead of')
|
104
104
|
|
105
105
|
_find_by_id(id, options)
|
106
106
|
end
|
107
107
|
|
108
108
|
# @private
|
109
109
|
def _find_all(ids, options = {})
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
110
|
+
ids = ids.map do |id|
|
111
|
+
if range_key
|
112
|
+
# expect [hash key, range key] pair
|
113
|
+
pk, sk = id
|
114
|
+
|
115
|
+
if pk.nil?
|
116
|
+
raise Errors::MissingHashKey
|
117
|
+
end
|
118
|
+
if sk.nil?
|
119
|
+
raise Errors::MissingRangeKey
|
120
|
+
end
|
121
|
+
|
122
|
+
pk_dumped = cast_and_dump(hash_key, pk)
|
123
|
+
sk_dumped = cast_and_dump(range_key, sk)
|
124
|
+
|
125
|
+
[pk_dumped, sk_dumped]
|
126
|
+
else
|
127
|
+
if id.nil?
|
128
|
+
raise Errors::MissingHashKey
|
129
|
+
end
|
116
130
|
|
117
|
-
|
131
|
+
cast_and_dump(hash_key, id)
|
118
132
|
end
|
119
133
|
end
|
120
134
|
|
@@ -144,7 +158,7 @@ module Dynamoid
|
|
144
158
|
models.each { |m| m.run_callbacks :find }
|
145
159
|
models
|
146
160
|
else
|
147
|
-
ids_list = range_key ? ids.map { |pk, sk| "(#{pk},#{sk})" } : ids.map(&:
|
161
|
+
ids_list = range_key ? ids.map { |pk, sk| "(#{pk.inspect},#{sk.inspect})" } : ids.map(&:inspect)
|
148
162
|
message = "Couldn't find all #{name.pluralize} with primary keys [#{ids_list.join(', ')}] "
|
149
163
|
message += "(found #{items.size} results, but was looking for #{ids.size})"
|
150
164
|
raise Errors::RecordNotFound, message
|
@@ -153,22 +167,21 @@ module Dynamoid
|
|
153
167
|
|
154
168
|
# @private
|
155
169
|
def _find_by_id(id, options = {})
|
170
|
+
raise Errors::MissingHashKey if id.nil?
|
156
171
|
raise Errors::MissingRangeKey if range_key && options[:range_key].nil?
|
157
172
|
|
158
|
-
|
159
|
-
key = options[:range_key]
|
160
|
-
key_casted = TypeCasting.cast_field(key, attributes[range_key])
|
161
|
-
key_dumped = Dumping.dump_field(key_casted, attributes[range_key])
|
173
|
+
partition_key_dumped = cast_and_dump(hash_key, id)
|
162
174
|
|
163
|
-
|
175
|
+
if range_key
|
176
|
+
options[:range_key] = cast_and_dump(range_key, options[:range_key])
|
164
177
|
end
|
165
178
|
|
166
|
-
if item = Dynamoid.adapter.read(table_name,
|
179
|
+
if item = Dynamoid.adapter.read(table_name, partition_key_dumped, options.slice(:range_key, :consistent_read))
|
167
180
|
model = from_database(item)
|
168
181
|
model.run_callbacks :find
|
169
182
|
model
|
170
183
|
elsif options[:raise_error]
|
171
|
-
primary_key = range_key ? "(#{id},#{options[:range_key]})" : id
|
184
|
+
primary_key = range_key ? "(#{id.inspect},#{options[:range_key].inspect})" : id.inspect
|
172
185
|
message = "Couldn't find #{name} with primary key #{primary_key}"
|
173
186
|
raise Errors::RecordNotFound, message
|
174
187
|
end
|
@@ -180,7 +193,7 @@ module Dynamoid
|
|
180
193
|
# @param range_key [Scalar value] range key of the object to find
|
181
194
|
#
|
182
195
|
def find_by_composite_key(hash_key, range_key, options = {})
|
183
|
-
|
196
|
+
Dynamoid.deprecator.warn('[Dynamoid] .find_by_composite_key is deprecated! Call .find instead of')
|
184
197
|
|
185
198
|
_find_by_id(hash_key, options.merge(range_key: range_key))
|
186
199
|
end
|
@@ -207,7 +220,7 @@ module Dynamoid
|
|
207
220
|
#
|
208
221
|
# @return [Array] an array of all matching items
|
209
222
|
def find_all_by_composite_key(hash_key, options = {})
|
210
|
-
|
223
|
+
Dynamoid.deprecator.warn('[Dynamoid] .find_all_composite_key is deprecated! Call .where instead of')
|
211
224
|
|
212
225
|
Dynamoid.adapter.query(table_name, options.merge(hash_value: hash_key)).flat_map { |i| i }.collect do |item|
|
213
226
|
from_database(item)
|
@@ -237,7 +250,7 @@ module Dynamoid
|
|
237
250
|
# @param options [Hash] conditions on range key e.g. +{ "rank.lte": 10 }, query filter, projected keys, scan_index_forward etc.
|
238
251
|
# @return [Array] an array of all matching items
|
239
252
|
def find_all_by_secondary_index(hash, options = {})
|
240
|
-
|
253
|
+
Dynamoid.deprecator.warn('[Dynamoid] .find_all_by_secondary_index is deprecated! Call .where instead of')
|
241
254
|
|
242
255
|
range = options[:range] || {}
|
243
256
|
hash_key_field, hash_key_value = hash.first
|
@@ -291,7 +304,7 @@ module Dynamoid
|
|
291
304
|
def method_missing(method, *args)
|
292
305
|
# Cannot use Symbol#start_with? because it was introduced in Ruby 2.7, but we support Ruby >= 2.3
|
293
306
|
if method.to_s.start_with?('find')
|
294
|
-
|
307
|
+
Dynamoid.deprecator.warn("[Dynamoid] .#{method} is deprecated! Call .where instead of")
|
295
308
|
|
296
309
|
finder = method.to_s.split('_by_').first
|
297
310
|
attributes = method.to_s.split('_by_').last.split('_and_')
|
@@ -308,6 +321,14 @@ module Dynamoid
|
|
308
321
|
super
|
309
322
|
end
|
310
323
|
end
|
324
|
+
|
325
|
+
private
|
326
|
+
|
327
|
+
def cast_and_dump(name, value)
|
328
|
+
attribute_options = attributes[name]
|
329
|
+
casted_value = TypeCasting.cast_field(value, attribute_options)
|
330
|
+
Dumping.dump_field(casted_value, attribute_options)
|
331
|
+
end
|
311
332
|
end
|
312
333
|
end
|
313
334
|
end
|
data/lib/dynamoid/loadable.rb
CHANGED
@@ -11,6 +11,7 @@ module Dynamoid
|
|
11
11
|
|
12
12
|
self
|
13
13
|
end
|
14
|
+
alias assign_attributes load
|
14
15
|
|
15
16
|
# Reload an object from the database -- if you suspect the object has changed in the data store and you need those
|
16
17
|
# changes to be reflected immediately, you would call this method. This is a consistent read.
|
@@ -1,56 +1,66 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'item_updater_with_casting_and_dumping'
|
4
|
+
|
3
5
|
module Dynamoid
|
4
6
|
module Persistence
|
5
7
|
# @private
|
6
8
|
class Inc
|
7
|
-
def self.call(model_class,
|
8
|
-
new(model_class,
|
9
|
+
def self.call(model_class, partition_key, sort_key = nil, counters)
|
10
|
+
new(model_class, partition_key, sort_key, counters).call
|
9
11
|
end
|
10
12
|
|
11
13
|
# rubocop:disable Style/OptionalArguments
|
12
|
-
def initialize(model_class,
|
14
|
+
def initialize(model_class, partition_key, sort_key = nil, counters)
|
13
15
|
@model_class = model_class
|
14
|
-
@
|
15
|
-
@
|
16
|
+
@partition_key = partition_key
|
17
|
+
@sort_key = sort_key
|
16
18
|
@counters = counters
|
17
19
|
end
|
18
20
|
# rubocop:enable Style/OptionalArguments
|
19
21
|
|
20
22
|
def call
|
21
23
|
touch = @counters.delete(:touch)
|
24
|
+
partition_key_dumped = cast_and_dump(@model_class.hash_key, @partition_key)
|
25
|
+
options = update_item_options(partition_key_dumped)
|
26
|
+
|
27
|
+
Dynamoid.adapter.update_item(@model_class.table_name, partition_key_dumped, options) do |t|
|
28
|
+
item_updater = ItemUpdaterWithCastingAndDumping.new(@model_class, t)
|
22
29
|
|
23
|
-
Dynamoid.adapter.update_item(@model_class.table_name, @hash_key, update_item_options) do |t|
|
24
30
|
@counters.each do |name, value|
|
25
|
-
|
31
|
+
item_updater.add(name => value)
|
26
32
|
end
|
27
33
|
|
28
34
|
if touch
|
29
35
|
value = DateTime.now.in_time_zone(Time.zone)
|
30
36
|
|
31
37
|
timestamp_attributes_to_touch(touch).each do |name|
|
32
|
-
|
38
|
+
item_updater.set(name => value)
|
33
39
|
end
|
34
40
|
end
|
35
41
|
end
|
42
|
+
rescue Dynamoid::Errors::ConditionalCheckFailedException # rubocop:disable Lint/SuppressedException
|
36
43
|
end
|
37
44
|
|
38
45
|
private
|
39
46
|
|
40
|
-
def update_item_options
|
47
|
+
def update_item_options(partition_key_dumped)
|
48
|
+
options = {}
|
49
|
+
|
50
|
+
conditions = {
|
51
|
+
if: {
|
52
|
+
@model_class.hash_key => partition_key_dumped
|
53
|
+
}
|
54
|
+
}
|
55
|
+
options[:conditions] = conditions
|
56
|
+
|
41
57
|
if @model_class.range_key
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
{ range_key: value_dumped }
|
46
|
-
else
|
47
|
-
{}
|
58
|
+
sort_key_dumped = cast_and_dump(@model_class.range_key, @sort_key)
|
59
|
+
options[:range_key] = sort_key_dumped
|
60
|
+
options[:conditions][@model_class.range_key] = sort_key_dumped
|
48
61
|
end
|
49
|
-
end
|
50
62
|
|
51
|
-
|
52
|
-
value_casted = TypeCasting.cast_field(value, @model_class.attributes[name])
|
53
|
-
Dumping.dump_field(value_casted, @model_class.attributes[name])
|
63
|
+
options
|
54
64
|
end
|
55
65
|
|
56
66
|
def timestamp_attributes_to_touch(touch)
|
@@ -61,6 +71,12 @@ module Dynamoid
|
|
61
71
|
names += Array.wrap(touch) if touch != true
|
62
72
|
names
|
63
73
|
end
|
74
|
+
|
75
|
+
def cast_and_dump(name, value)
|
76
|
+
options = @model_class.attributes[name]
|
77
|
+
value_casted = TypeCasting.cast_field(value, options)
|
78
|
+
Dumping.dump_field(value_casted, options)
|
79
|
+
end
|
64
80
|
end
|
65
81
|
end
|
66
82
|
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dynamoid
|
4
|
+
module Persistence
|
5
|
+
# @private
|
6
|
+
class ItemUpdaterWithCastingAndDumping
|
7
|
+
def initialize(model_class, item_updater)
|
8
|
+
@model_class = model_class
|
9
|
+
@item_updater = item_updater
|
10
|
+
end
|
11
|
+
|
12
|
+
def add(attributes)
|
13
|
+
@item_updater.add(cast_and_dump(attributes))
|
14
|
+
end
|
15
|
+
|
16
|
+
def set(attributes)
|
17
|
+
@item_updater.set(cast_and_dump(attributes))
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def cast_and_dump(attributes)
|
23
|
+
casted_and_dumped = {}
|
24
|
+
|
25
|
+
attributes.each do |name, value|
|
26
|
+
value_casted = TypeCasting.cast_field(value, @model_class.attributes[name])
|
27
|
+
value_dumped = Dumping.dump_field(value_casted, @model_class.attributes[name])
|
28
|
+
|
29
|
+
casted_and_dumped[name] = value_dumped
|
30
|
+
end
|
31
|
+
|
32
|
+
casted_and_dumped
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dynamoid
|
4
|
+
module Persistence
|
5
|
+
# @private
|
6
|
+
class ItemUpdaterWithDumping
|
7
|
+
def initialize(model_class, item_updater)
|
8
|
+
@model_class = model_class
|
9
|
+
@item_updater = item_updater
|
10
|
+
end
|
11
|
+
|
12
|
+
def add(attributes)
|
13
|
+
@item_updater.add(dump(attributes))
|
14
|
+
end
|
15
|
+
|
16
|
+
def set(attributes)
|
17
|
+
@item_updater.set(dump(attributes))
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def dump(attributes)
|
23
|
+
dumped = {}
|
24
|
+
|
25
|
+
attributes.each do |name, value|
|
26
|
+
dumped[name] = Dumping.dump_field(value, @model_class.attributes[name])
|
27
|
+
end
|
28
|
+
|
29
|
+
dumped
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'item_updater_with_dumping'
|
4
|
+
|
3
5
|
module Dynamoid
|
4
6
|
module Persistence
|
5
7
|
# @private
|
@@ -10,10 +12,12 @@ module Dynamoid
|
|
10
12
|
|
11
13
|
def initialize(model, touch: nil)
|
12
14
|
@model = model
|
13
|
-
@touch = touch # touch
|
15
|
+
@touch = touch # `touch: false` means explicit disabling of updating the `updated_at` attribute
|
14
16
|
end
|
15
17
|
|
16
18
|
def call
|
19
|
+
validate_primary_key!
|
20
|
+
|
17
21
|
@model.hash_key = SecureRandom.uuid if @model.hash_key.blank?
|
18
22
|
|
19
23
|
return true unless @model.changed?
|
@@ -34,11 +38,14 @@ module Dynamoid
|
|
34
38
|
Dynamoid.adapter.write(@model.class.table_name, attributes_dumped, conditions_for_write)
|
35
39
|
else
|
36
40
|
attributes_to_persist = @model.attributes.slice(*@model.changed.map(&:to_sym))
|
41
|
+
partition_key_dumped = dump(@model.class.hash_key, @model.hash_key)
|
42
|
+
options = options_to_update_item(partition_key_dumped)
|
43
|
+
|
44
|
+
Dynamoid.adapter.update_item(@model.class.table_name, partition_key_dumped, options) do |t|
|
45
|
+
item_updater = ItemUpdaterWithDumping.new(@model.class, t)
|
37
46
|
|
38
|
-
Dynamoid.adapter.update_item(@model.class.table_name, @model.hash_key, options_to_update_item) do |t|
|
39
47
|
attributes_to_persist.each do |name, value|
|
40
|
-
|
41
|
-
t.set(name => value_dumped)
|
48
|
+
item_updater.set(name => value)
|
42
49
|
end
|
43
50
|
end
|
44
51
|
end
|
@@ -55,22 +62,25 @@ module Dynamoid
|
|
55
62
|
|
56
63
|
private
|
57
64
|
|
65
|
+
def validate_primary_key!
|
66
|
+
raise Dynamoid::Errors::MissingHashKey if !@model.new_record? && @model.hash_key.nil?
|
67
|
+
raise Dynamoid::Errors::MissingRangeKey if @model.class.range_key? && @model.range_value.nil?
|
68
|
+
end
|
69
|
+
|
58
70
|
# Should be called after incrementing `lock_version` attribute
|
59
71
|
def conditions_for_write
|
60
72
|
conditions = {}
|
61
73
|
|
62
74
|
# Add an 'exists' check to prevent overwriting existing records with new ones
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
conditions[:unless_exists] << @model.range_key
|
67
|
-
end
|
75
|
+
conditions[:unless_exists] = [@model.class.hash_key]
|
76
|
+
if @model.range_key
|
77
|
+
conditions[:unless_exists] << @model.range_key
|
68
78
|
end
|
69
79
|
|
70
80
|
# Add an optimistic locking check if the lock_version column exists
|
71
81
|
# Uses the original lock_version value from Dirty API
|
72
82
|
# in case user changed 'lock_version' manually
|
73
|
-
if @model.class.attributes[:lock_version] &&
|
83
|
+
if @model.class.attributes[:lock_version] && @model.changes[:lock_version][0]
|
74
84
|
conditions[:if] ||= {}
|
75
85
|
conditions[:if][:lock_version] = @model.changes[:lock_version][0]
|
76
86
|
end
|
@@ -78,22 +88,22 @@ module Dynamoid
|
|
78
88
|
conditions
|
79
89
|
end
|
80
90
|
|
81
|
-
def options_to_update_item
|
91
|
+
def options_to_update_item(partition_key_dumped)
|
82
92
|
options = {}
|
83
93
|
|
84
94
|
if @model.class.range_key
|
85
|
-
value_dumped =
|
95
|
+
value_dumped = dump(@model.class.range_key, @model.range_value)
|
86
96
|
options[:range_key] = value_dumped
|
87
97
|
end
|
88
98
|
|
89
99
|
conditions = {}
|
90
100
|
conditions[:if] ||= {}
|
91
|
-
conditions[:if][@model.class.hash_key] =
|
101
|
+
conditions[:if][@model.class.hash_key] = partition_key_dumped
|
92
102
|
|
93
103
|
# Add an optimistic locking check if the lock_version column exists
|
94
104
|
# Uses the original lock_version value from Dirty API
|
95
105
|
# in case user changed 'lock_version' manually
|
96
|
-
if @model.class.attributes[:lock_version] &&
|
106
|
+
if @model.class.attributes[:lock_version] && @model.changes[:lock_version][0]
|
97
107
|
conditions[:if] ||= {}
|
98
108
|
conditions[:if][:lock_version] = @model.changes[:lock_version][0]
|
99
109
|
end
|
@@ -102,6 +112,11 @@ module Dynamoid
|
|
102
112
|
|
103
113
|
options
|
104
114
|
end
|
115
|
+
|
116
|
+
def dump(name, value)
|
117
|
+
options = @model.class.attributes[name]
|
118
|
+
Dumping.dump_field(value, options)
|
119
|
+
end
|
105
120
|
end
|
106
121
|
end
|
107
122
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'item_updater_with_casting_and_dumping'
|
4
|
+
|
3
5
|
module Dynamoid
|
4
6
|
module Persistence
|
5
7
|
# @private
|
@@ -14,9 +16,12 @@ module Dynamoid
|
|
14
16
|
@sort_key = sort_key
|
15
17
|
@attributes = attributes.symbolize_keys
|
16
18
|
@conditions = conditions
|
19
|
+
|
20
|
+
@partition_key_dumped = cast_and_dump(@model_class.hash_key, @partition_key)
|
17
21
|
end
|
18
22
|
|
19
23
|
def call
|
24
|
+
validate_primary_key!
|
20
25
|
UpdateValidations.validate_attributes_exist(@model_class, @attributes)
|
21
26
|
|
22
27
|
if @model_class.timestamps_enabled?
|
@@ -30,12 +35,17 @@ module Dynamoid
|
|
30
35
|
|
31
36
|
private
|
32
37
|
|
38
|
+
def validate_primary_key!
|
39
|
+
raise Dynamoid::Errors::MissingHashKey if @partition_key.nil?
|
40
|
+
raise Dynamoid::Errors::MissingRangeKey if @model_class.range_key? && @sort_key.nil?
|
41
|
+
end
|
42
|
+
|
33
43
|
def update_item
|
34
|
-
Dynamoid.adapter.update_item(@model_class.table_name, @
|
44
|
+
Dynamoid.adapter.update_item(@model_class.table_name, @partition_key_dumped, options_to_update_item) do |t|
|
45
|
+
item_updater = ItemUpdaterWithCastingAndDumping.new(@model_class, t)
|
46
|
+
|
35
47
|
@attributes.each do |k, v|
|
36
|
-
|
37
|
-
value_dumped = Dumping.dump_field(value_casted, @model_class.attributes[k])
|
38
|
-
t.set(k => value_dumped)
|
48
|
+
item_updater.set(k => v)
|
39
49
|
end
|
40
50
|
end
|
41
51
|
end
|
@@ -48,18 +58,23 @@ module Dynamoid
|
|
48
58
|
options = {}
|
49
59
|
|
50
60
|
if @model_class.range_key
|
51
|
-
|
52
|
-
|
53
|
-
options[:range_key] = value_dumped
|
61
|
+
range_key_dumped = cast_and_dump(@model_class.range_key, @sort_key)
|
62
|
+
options[:range_key] = range_key_dumped
|
54
63
|
end
|
55
64
|
|
56
65
|
conditions = @conditions.deep_dup
|
57
66
|
conditions[:if] ||= {}
|
58
|
-
conditions[:if][@model_class.hash_key] = @
|
67
|
+
conditions[:if][@model_class.hash_key] = @partition_key_dumped
|
59
68
|
options[:conditions] = conditions
|
60
69
|
|
61
70
|
options
|
62
71
|
end
|
72
|
+
|
73
|
+
def cast_and_dump(name, value)
|
74
|
+
options = @model_class.attributes[name]
|
75
|
+
value_casted = TypeCasting.cast_field(value, options)
|
76
|
+
Dumping.dump_field(value_casted, options)
|
77
|
+
end
|
63
78
|
end
|
64
79
|
end
|
65
80
|
end
|
@@ -7,9 +7,9 @@ module Dynamoid
|
|
7
7
|
def self.validate_attributes_exist(model_class, attributes)
|
8
8
|
model_attributes = model_class.attributes.keys
|
9
9
|
|
10
|
-
attributes.each_key do |
|
11
|
-
unless model_attributes.include?(
|
12
|
-
raise Dynamoid::Errors::UnknownAttribute,
|
10
|
+
attributes.each_key do |name|
|
11
|
+
unless model_attributes.include?(name)
|
12
|
+
raise Dynamoid::Errors::UnknownAttribute.new(model_class, name)
|
13
13
|
end
|
14
14
|
end
|
15
15
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'item_updater_with_casting_and_dumping'
|
4
|
+
|
3
5
|
module Dynamoid
|
4
6
|
module Persistence
|
5
7
|
# @private
|
@@ -17,6 +19,7 @@ module Dynamoid
|
|
17
19
|
end
|
18
20
|
|
19
21
|
def call
|
22
|
+
validate_primary_key!
|
20
23
|
UpdateValidations.validate_attributes_exist(@model_class, @attributes)
|
21
24
|
|
22
25
|
if @model_class.timestamps_enabled?
|
@@ -30,13 +33,19 @@ module Dynamoid
|
|
30
33
|
|
31
34
|
private
|
32
35
|
|
36
|
+
def validate_primary_key!
|
37
|
+
raise Dynamoid::Errors::MissingHashKey if @partition_key.nil?
|
38
|
+
raise Dynamoid::Errors::MissingRangeKey if @model_class.range_key? && @sort_key.nil?
|
39
|
+
end
|
40
|
+
|
33
41
|
def update_item
|
34
|
-
|
35
|
-
@attributes.each do |k, v|
|
36
|
-
value_casted = TypeCasting.cast_field(v, @model_class.attributes[k])
|
37
|
-
value_dumped = Dumping.dump_field(value_casted, @model_class.attributes[k])
|
42
|
+
partition_key_dumped = cast_and_dump(@model_class.hash_key, @partition_key)
|
38
43
|
|
39
|
-
|
44
|
+
Dynamoid.adapter.update_item(@model_class.table_name, partition_key_dumped, options_to_update_item) do |t|
|
45
|
+
item_updater = ItemUpdaterWithCastingAndDumping.new(@model_class, t)
|
46
|
+
|
47
|
+
@attributes.each do |k, v|
|
48
|
+
item_updater.set(k => v)
|
40
49
|
end
|
41
50
|
end
|
42
51
|
end
|
@@ -45,9 +54,8 @@ module Dynamoid
|
|
45
54
|
options = {}
|
46
55
|
|
47
56
|
if @model_class.range_key
|
48
|
-
|
49
|
-
|
50
|
-
options[:range_key] = value_dumped
|
57
|
+
range_key_dumped = cast_and_dump(@model_class.range_key, @sort_key)
|
58
|
+
options[:range_key] = range_key_dumped
|
51
59
|
end
|
52
60
|
|
53
61
|
options[:conditions] = @conditions
|
@@ -57,6 +65,12 @@ module Dynamoid
|
|
57
65
|
def undump_attributes(raw_attributes)
|
58
66
|
Undumping.undump_attributes(raw_attributes, @model_class.attributes)
|
59
67
|
end
|
68
|
+
|
69
|
+
def cast_and_dump(name, value)
|
70
|
+
options = @model_class.attributes[name]
|
71
|
+
value_casted = TypeCasting.cast_field(value, options)
|
72
|
+
Dumping.dump_field(value_casted, options)
|
73
|
+
end
|
60
74
|
end
|
61
75
|
end
|
62
76
|
end
|