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/fields.rb
CHANGED
|
@@ -134,6 +134,11 @@ module Dynamoid
|
|
|
134
134
|
# @param name [Symbol] name of the field
|
|
135
135
|
# @param type [Symbol] type of the field (optional)
|
|
136
136
|
# @param options [Hash] any additional options for the field type (optional)
|
|
137
|
+
# @option options [Symbol] :of <description>
|
|
138
|
+
# @option options [Symbol] :store_as_string <description>
|
|
139
|
+
# @option options [Symbol] :store_as_native_boolean <description>
|
|
140
|
+
# @option options [Symbol] :default <description>
|
|
141
|
+
# @option options [Symbol] :alias <description>
|
|
137
142
|
#
|
|
138
143
|
# @since 0.2.0
|
|
139
144
|
def field(name, type = :string, options = {})
|
|
@@ -197,10 +202,22 @@ module Dynamoid
|
|
|
197
202
|
# field :id, :integer
|
|
198
203
|
# end
|
|
199
204
|
#
|
|
205
|
+
# To declare a new attribute with not-default type as a table hash key a
|
|
206
|
+
# :key_type option can be used:
|
|
207
|
+
#
|
|
208
|
+
# class User
|
|
209
|
+
# include Dynamoid::Document
|
|
210
|
+
#
|
|
211
|
+
# table key: :user_id, key_type: :integer
|
|
212
|
+
# end
|
|
213
|
+
#
|
|
200
214
|
# @param options [Hash] options to override default table settings
|
|
201
215
|
# @option options [Symbol] :name name of a table
|
|
216
|
+
# @option options [Symbol] :arn table ARN; it allows referring tables in another AWS accounts; has higher priority than the +name+ option
|
|
202
217
|
# @option options [Symbol] :key name of a hash key attribute
|
|
218
|
+
# @option options [Symbol] :key_type type of a hash key attribute
|
|
203
219
|
# @option options [Symbol] :inheritance_field name of an attribute used for STI
|
|
220
|
+
# @option options [Array<Symbol>] :skip_generating_fields don't generate implicitly methods with given names, e.g. +:id+, +:created_at+, +:updated_at+
|
|
204
221
|
# @option options [Symbol] :capacity_mode table billing mode - either +provisioned+ or +on_demand+
|
|
205
222
|
# @option options [Integer] :write_capacity table write capacity units
|
|
206
223
|
# @option options [Integer] :read_capacity table read capacity units
|
|
@@ -211,10 +228,17 @@ module Dynamoid
|
|
|
211
228
|
def table(options)
|
|
212
229
|
self.options = options
|
|
213
230
|
|
|
231
|
+
id_already_declared = true
|
|
232
|
+
timestamps_already_declared = Dynamoid::Config.timestamps
|
|
233
|
+
|
|
214
234
|
# a default 'id' column is created when Dynamoid::Document is included
|
|
215
235
|
unless attributes.key? hash_key
|
|
216
236
|
remove_field :id
|
|
217
|
-
|
|
237
|
+
id_already_declared = false
|
|
238
|
+
|
|
239
|
+
key_type = options[:key_type] || :string
|
|
240
|
+
field(hash_key, key_type)
|
|
241
|
+
id_already_declared = hash_key == :id
|
|
218
242
|
end
|
|
219
243
|
|
|
220
244
|
# The created_at/updated_at fields are declared in the `included` callback first.
|
|
@@ -223,14 +247,30 @@ module Dynamoid
|
|
|
223
247
|
# So we need to make decision again and declare the fields or rollback thier declaration.
|
|
224
248
|
#
|
|
225
249
|
# Do not replace with `#timestamps_enabled?`.
|
|
226
|
-
if options[:timestamps] && !
|
|
250
|
+
if options[:timestamps] && !timestamps_already_declared
|
|
227
251
|
# The fields weren't declared in `included` callback because they are disabled globaly
|
|
228
252
|
field :created_at, :datetime
|
|
229
253
|
field :updated_at, :datetime
|
|
230
|
-
|
|
254
|
+
|
|
255
|
+
timestamps_already_declared = true
|
|
256
|
+
elsif options[:timestamps] == false && timestamps_already_declared
|
|
231
257
|
# The fields were declared in `included` callback but they are disabled for a table
|
|
232
258
|
remove_field :created_at
|
|
233
259
|
remove_field :updated_at
|
|
260
|
+
|
|
261
|
+
timestamps_already_declared = false
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
# handle :skip_generating_fields option
|
|
265
|
+
skip_generating_fields = options[:skip_generating_fields] || []
|
|
266
|
+
if id_already_declared && skip_generating_fields.include?(:id)
|
|
267
|
+
remove_field :id
|
|
268
|
+
end
|
|
269
|
+
if timestamps_already_declared && skip_generating_fields.include?(:created_at)
|
|
270
|
+
remove_field :created_at
|
|
271
|
+
end
|
|
272
|
+
if timestamps_already_declared && skip_generating_fields.include?(:updated_at)
|
|
273
|
+
remove_field :updated_at
|
|
234
274
|
end
|
|
235
275
|
end
|
|
236
276
|
|
|
@@ -291,7 +331,7 @@ module Dynamoid
|
|
|
291
331
|
old_value = read_attribute(name)
|
|
292
332
|
|
|
293
333
|
unless attribute_is_present_on_model?(name)
|
|
294
|
-
raise Dynamoid::Errors::UnknownAttribute,
|
|
334
|
+
raise Dynamoid::Errors::UnknownAttribute.new(self.class, name)
|
|
295
335
|
end
|
|
296
336
|
|
|
297
337
|
if association = @associations[name]
|
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+
|
|
@@ -107,24 +107,40 @@ module Dynamoid
|
|
|
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
|
|
|
121
135
|
read_options = options.slice(:consistent_read)
|
|
136
|
+
table = Dynamoid.adapter.describe_table(table_name)
|
|
122
137
|
|
|
123
138
|
items = if Dynamoid.config.backoff
|
|
124
139
|
items = []
|
|
125
140
|
backoff = nil
|
|
126
141
|
Dynamoid.adapter.read(table_name, ids, read_options) do |hash, has_unprocessed_items|
|
|
127
|
-
|
|
142
|
+
# Cannot use #table_name because it may contain a table ARN, but response contains always a table name
|
|
143
|
+
items += hash[table.name]
|
|
128
144
|
|
|
129
145
|
if has_unprocessed_items
|
|
130
146
|
backoff ||= Dynamoid.config.build_backoff
|
|
@@ -136,7 +152,9 @@ module Dynamoid
|
|
|
136
152
|
items
|
|
137
153
|
else
|
|
138
154
|
items = Dynamoid.adapter.read(table_name, ids, read_options)
|
|
139
|
-
|
|
155
|
+
|
|
156
|
+
# Cannot use #table_name because it may contain a table ARN, but response contains always a table name
|
|
157
|
+
items ? items[table.name] : []
|
|
140
158
|
end
|
|
141
159
|
|
|
142
160
|
if items.size == ids.size || !options[:raise_error]
|
|
@@ -144,7 +162,7 @@ module Dynamoid
|
|
|
144
162
|
models.each { |m| m.run_callbacks :find }
|
|
145
163
|
models
|
|
146
164
|
else
|
|
147
|
-
ids_list = range_key ? ids.map { |pk, sk| "(#{pk},#{sk})" } : ids.map(&:
|
|
165
|
+
ids_list = range_key ? ids.map { |pk, sk| "(#{pk.inspect},#{sk.inspect})" } : ids.map(&:inspect)
|
|
148
166
|
message = "Couldn't find all #{name.pluralize} with primary keys [#{ids_list.join(', ')}] "
|
|
149
167
|
message += "(found #{items.size} results, but was looking for #{ids.size})"
|
|
150
168
|
raise Errors::RecordNotFound, message
|
|
@@ -153,22 +171,21 @@ module Dynamoid
|
|
|
153
171
|
|
|
154
172
|
# @private
|
|
155
173
|
def _find_by_id(id, options = {})
|
|
174
|
+
raise Errors::MissingHashKey if id.nil?
|
|
156
175
|
raise Errors::MissingRangeKey if range_key && options[:range_key].nil?
|
|
157
176
|
|
|
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])
|
|
177
|
+
partition_key_dumped = cast_and_dump(hash_key, id)
|
|
162
178
|
|
|
163
|
-
|
|
179
|
+
if range_key
|
|
180
|
+
options[:range_key] = cast_and_dump(range_key, options[:range_key])
|
|
164
181
|
end
|
|
165
182
|
|
|
166
|
-
if item = Dynamoid.adapter.read(table_name,
|
|
183
|
+
if item = Dynamoid.adapter.read(table_name, partition_key_dumped, options.slice(:range_key, :consistent_read))
|
|
167
184
|
model = from_database(item)
|
|
168
185
|
model.run_callbacks :find
|
|
169
186
|
model
|
|
170
187
|
elsif options[:raise_error]
|
|
171
|
-
primary_key = range_key ? "(#{id},#{options[:range_key]})" : id
|
|
188
|
+
primary_key = range_key ? "(#{id.inspect},#{options[:range_key].inspect})" : id.inspect
|
|
172
189
|
message = "Couldn't find #{name} with primary key #{primary_key}"
|
|
173
190
|
raise Errors::RecordNotFound, message
|
|
174
191
|
end
|
|
@@ -308,6 +325,14 @@ module Dynamoid
|
|
|
308
325
|
super
|
|
309
326
|
end
|
|
310
327
|
end
|
|
328
|
+
|
|
329
|
+
private
|
|
330
|
+
|
|
331
|
+
def cast_and_dump(name, value)
|
|
332
|
+
attribute_options = attributes[name]
|
|
333
|
+
casted_value = TypeCasting.cast_field(value, attribute_options)
|
|
334
|
+
Dumping.dump_field(casted_value, attribute_options)
|
|
335
|
+
end
|
|
311
336
|
end
|
|
312
337
|
end
|
|
313
338
|
end
|
|
@@ -6,23 +6,25 @@ module Dynamoid
|
|
|
6
6
|
module Persistence
|
|
7
7
|
# @private
|
|
8
8
|
class Inc
|
|
9
|
-
def self.call(model_class,
|
|
10
|
-
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
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
# rubocop:disable Style/OptionalArguments
|
|
14
|
-
def initialize(model_class,
|
|
14
|
+
def initialize(model_class, partition_key, sort_key = nil, counters)
|
|
15
15
|
@model_class = model_class
|
|
16
|
-
@
|
|
17
|
-
@
|
|
16
|
+
@partition_key = partition_key
|
|
17
|
+
@sort_key = sort_key
|
|
18
18
|
@counters = counters
|
|
19
19
|
end
|
|
20
20
|
# rubocop:enable Style/OptionalArguments
|
|
21
21
|
|
|
22
22
|
def call
|
|
23
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)
|
|
24
26
|
|
|
25
|
-
Dynamoid.adapter.update_item(@model_class.table_name,
|
|
27
|
+
Dynamoid.adapter.update_item(@model_class.table_name, partition_key_dumped, options) do |t|
|
|
26
28
|
item_updater = ItemUpdaterWithCastingAndDumping.new(@model_class, t)
|
|
27
29
|
|
|
28
30
|
@counters.each do |name, value|
|
|
@@ -37,19 +39,28 @@ module Dynamoid
|
|
|
37
39
|
end
|
|
38
40
|
end
|
|
39
41
|
end
|
|
42
|
+
rescue Dynamoid::Errors::ConditionalCheckFailedException # rubocop:disable Lint/SuppressedException
|
|
40
43
|
end
|
|
41
44
|
|
|
42
45
|
private
|
|
43
46
|
|
|
44
|
-
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
|
+
|
|
45
57
|
if @model_class.range_key
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
{ range_key: value_dumped }
|
|
50
|
-
else
|
|
51
|
-
{}
|
|
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
|
|
52
61
|
end
|
|
62
|
+
|
|
63
|
+
options
|
|
53
64
|
end
|
|
54
65
|
|
|
55
66
|
def timestamp_attributes_to_touch(touch)
|
|
@@ -60,6 +71,12 @@ module Dynamoid
|
|
|
60
71
|
names += Array.wrap(touch) if touch != true
|
|
61
72
|
names
|
|
62
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
|
|
63
80
|
end
|
|
64
81
|
end
|
|
65
82
|
end
|
|
@@ -12,10 +12,12 @@ module Dynamoid
|
|
|
12
12
|
|
|
13
13
|
def initialize(model, touch: nil)
|
|
14
14
|
@model = model
|
|
15
|
-
@touch = touch # touch
|
|
15
|
+
@touch = touch # `touch: false` means explicit disabling of updating the `updated_at` attribute
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def call
|
|
19
|
+
validate_primary_key!
|
|
20
|
+
|
|
19
21
|
@model.hash_key = SecureRandom.uuid if @model.hash_key.blank?
|
|
20
22
|
|
|
21
23
|
return true unless @model.changed?
|
|
@@ -36,8 +38,10 @@ module Dynamoid
|
|
|
36
38
|
Dynamoid.adapter.write(@model.class.table_name, attributes_dumped, conditions_for_write)
|
|
37
39
|
else
|
|
38
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)
|
|
39
43
|
|
|
40
|
-
Dynamoid.adapter.update_item(@model.class.table_name,
|
|
44
|
+
Dynamoid.adapter.update_item(@model.class.table_name, partition_key_dumped, options) do |t|
|
|
41
45
|
item_updater = ItemUpdaterWithDumping.new(@model.class, t)
|
|
42
46
|
|
|
43
47
|
attributes_to_persist.each do |name, value|
|
|
@@ -58,22 +62,25 @@ module Dynamoid
|
|
|
58
62
|
|
|
59
63
|
private
|
|
60
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
|
+
|
|
61
70
|
# Should be called after incrementing `lock_version` attribute
|
|
62
71
|
def conditions_for_write
|
|
63
72
|
conditions = {}
|
|
64
73
|
|
|
65
74
|
# Add an 'exists' check to prevent overwriting existing records with new ones
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
conditions[:unless_exists] << @model.range_key
|
|
70
|
-
end
|
|
75
|
+
conditions[:unless_exists] = [@model.class.hash_key]
|
|
76
|
+
if @model.range_key
|
|
77
|
+
conditions[:unless_exists] << @model.range_key
|
|
71
78
|
end
|
|
72
79
|
|
|
73
80
|
# Add an optimistic locking check if the lock_version column exists
|
|
74
81
|
# Uses the original lock_version value from Dirty API
|
|
75
82
|
# in case user changed 'lock_version' manually
|
|
76
|
-
if @model.class.attributes[:lock_version] &&
|
|
83
|
+
if @model.class.attributes[:lock_version] && @model.changes[:lock_version][0]
|
|
77
84
|
conditions[:if] ||= {}
|
|
78
85
|
conditions[:if][:lock_version] = @model.changes[:lock_version][0]
|
|
79
86
|
end
|
|
@@ -81,22 +88,22 @@ module Dynamoid
|
|
|
81
88
|
conditions
|
|
82
89
|
end
|
|
83
90
|
|
|
84
|
-
def options_to_update_item
|
|
91
|
+
def options_to_update_item(partition_key_dumped)
|
|
85
92
|
options = {}
|
|
86
93
|
|
|
87
94
|
if @model.class.range_key
|
|
88
|
-
value_dumped =
|
|
95
|
+
value_dumped = dump(@model.class.range_key, @model.range_value)
|
|
89
96
|
options[:range_key] = value_dumped
|
|
90
97
|
end
|
|
91
98
|
|
|
92
99
|
conditions = {}
|
|
93
100
|
conditions[:if] ||= {}
|
|
94
|
-
conditions[:if][@model.class.hash_key] =
|
|
101
|
+
conditions[:if][@model.class.hash_key] = partition_key_dumped
|
|
95
102
|
|
|
96
103
|
# Add an optimistic locking check if the lock_version column exists
|
|
97
104
|
# Uses the original lock_version value from Dirty API
|
|
98
105
|
# in case user changed 'lock_version' manually
|
|
99
|
-
if @model.class.attributes[:lock_version] &&
|
|
106
|
+
if @model.class.attributes[:lock_version] && @model.changes[:lock_version][0]
|
|
100
107
|
conditions[:if] ||= {}
|
|
101
108
|
conditions[:if][:lock_version] = @model.changes[:lock_version][0]
|
|
102
109
|
end
|
|
@@ -105,6 +112,11 @@ module Dynamoid
|
|
|
105
112
|
|
|
106
113
|
options
|
|
107
114
|
end
|
|
115
|
+
|
|
116
|
+
def dump(name, value)
|
|
117
|
+
options = @model.class.attributes[name]
|
|
118
|
+
Dumping.dump_field(value, options)
|
|
119
|
+
end
|
|
108
120
|
end
|
|
109
121
|
end
|
|
110
122
|
end
|
|
@@ -16,9 +16,12 @@ module Dynamoid
|
|
|
16
16
|
@sort_key = sort_key
|
|
17
17
|
@attributes = attributes.symbolize_keys
|
|
18
18
|
@conditions = conditions
|
|
19
|
+
|
|
20
|
+
@partition_key_dumped = cast_and_dump(@model_class.hash_key, @partition_key)
|
|
19
21
|
end
|
|
20
22
|
|
|
21
23
|
def call
|
|
24
|
+
validate_primary_key!
|
|
22
25
|
UpdateValidations.validate_attributes_exist(@model_class, @attributes)
|
|
23
26
|
|
|
24
27
|
if @model_class.timestamps_enabled?
|
|
@@ -32,8 +35,13 @@ module Dynamoid
|
|
|
32
35
|
|
|
33
36
|
private
|
|
34
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
|
+
|
|
35
43
|
def update_item
|
|
36
|
-
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|
|
|
37
45
|
item_updater = ItemUpdaterWithCastingAndDumping.new(@model_class, t)
|
|
38
46
|
|
|
39
47
|
@attributes.each do |k, v|
|
|
@@ -50,18 +58,23 @@ module Dynamoid
|
|
|
50
58
|
options = {}
|
|
51
59
|
|
|
52
60
|
if @model_class.range_key
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
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
|
|
56
63
|
end
|
|
57
64
|
|
|
58
65
|
conditions = @conditions.deep_dup
|
|
59
66
|
conditions[:if] ||= {}
|
|
60
|
-
conditions[:if][@model_class.hash_key] = @
|
|
67
|
+
conditions[:if][@model_class.hash_key] = @partition_key_dumped
|
|
61
68
|
options[:conditions] = conditions
|
|
62
69
|
|
|
63
70
|
options
|
|
64
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
|
|
65
78
|
end
|
|
66
79
|
end
|
|
67
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
|
|
@@ -19,6 +19,7 @@ module Dynamoid
|
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
def call
|
|
22
|
+
validate_primary_key!
|
|
22
23
|
UpdateValidations.validate_attributes_exist(@model_class, @attributes)
|
|
23
24
|
|
|
24
25
|
if @model_class.timestamps_enabled?
|
|
@@ -32,8 +33,15 @@ module Dynamoid
|
|
|
32
33
|
|
|
33
34
|
private
|
|
34
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
|
+
|
|
35
41
|
def update_item
|
|
36
|
-
|
|
42
|
+
partition_key_dumped = cast_and_dump(@model_class.hash_key, @partition_key)
|
|
43
|
+
|
|
44
|
+
Dynamoid.adapter.update_item(@model_class.table_name, partition_key_dumped, options_to_update_item) do |t|
|
|
37
45
|
item_updater = ItemUpdaterWithCastingAndDumping.new(@model_class, t)
|
|
38
46
|
|
|
39
47
|
@attributes.each do |k, v|
|
|
@@ -46,9 +54,8 @@ module Dynamoid
|
|
|
46
54
|
options = {}
|
|
47
55
|
|
|
48
56
|
if @model_class.range_key
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
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
|
|
52
59
|
end
|
|
53
60
|
|
|
54
61
|
options[:conditions] = @conditions
|
|
@@ -58,6 +65,12 @@ module Dynamoid
|
|
|
58
65
|
def undump_attributes(raw_attributes)
|
|
59
66
|
Undumping.undump_attributes(raw_attributes, @model_class.attributes)
|
|
60
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
|
|
61
74
|
end
|
|
62
75
|
end
|
|
63
76
|
end
|