dynamoid 3.2.0 → 3.3.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/.travis.yml +9 -6
- data/Appraisals +8 -14
- data/CHANGELOG.md +24 -0
- data/README.md +493 -228
- data/gemfiles/rails_4_2.gemfile +5 -7
- data/gemfiles/rails_5_0.gemfile +4 -6
- data/gemfiles/rails_5_1.gemfile +4 -6
- data/gemfiles/rails_5_2.gemfile +4 -6
- data/gemfiles/rails_6_0.gemfile +8 -0
- data/lib/dynamoid.rb +1 -0
- data/lib/dynamoid/adapter.rb +3 -10
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3.rb +25 -69
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/batch_get_item.rb +105 -0
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/create_table.rb +9 -4
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/query.rb +11 -4
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/scan.rb +11 -3
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/until_past_table_status.rb +3 -2
- data/lib/dynamoid/components.rb +6 -3
- data/lib/dynamoid/config.rb +1 -0
- data/lib/dynamoid/criteria.rb +1 -1
- data/lib/dynamoid/criteria/chain.rb +33 -6
- data/lib/dynamoid/criteria/key_fields_detector.rb +101 -32
- data/lib/dynamoid/dirty.rb +186 -34
- data/lib/dynamoid/document.rb +8 -216
- data/lib/dynamoid/fields.rb +8 -0
- data/lib/dynamoid/loadable.rb +31 -0
- data/lib/dynamoid/persistence.rb +177 -85
- data/lib/dynamoid/persistence/import.rb +72 -0
- data/lib/dynamoid/persistence/save.rb +63 -0
- data/lib/dynamoid/persistence/update_fields.rb +62 -0
- data/lib/dynamoid/persistence/upsert.rb +60 -0
- data/lib/dynamoid/version.rb +1 -1
- metadata +9 -2
data/lib/dynamoid/document.rb
CHANGED
@@ -70,36 +70,6 @@ module Dynamoid #:nodoc:
|
|
70
70
|
Dynamoid.adapter.count(table_name)
|
71
71
|
end
|
72
72
|
|
73
|
-
# Initialize a new object and immediately save it to the database.
|
74
|
-
#
|
75
|
-
# @param [Hash] attrs Attributes with which to create the object.
|
76
|
-
#
|
77
|
-
# @return [Dynamoid::Document] the saved document
|
78
|
-
#
|
79
|
-
# @since 0.2.0
|
80
|
-
def create(attrs = {})
|
81
|
-
if attrs.is_a?(Array)
|
82
|
-
attrs.map { |attr| create(attr) }
|
83
|
-
else
|
84
|
-
build(attrs).tap(&:save)
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
# Initialize a new object and immediately save it to the database. Raise an exception if persistence failed.
|
89
|
-
#
|
90
|
-
# @param [Hash] attrs Attributes with which to create the object.
|
91
|
-
#
|
92
|
-
# @return [Dynamoid::Document] the saved document
|
93
|
-
#
|
94
|
-
# @since 0.2.0
|
95
|
-
def create!(attrs = {})
|
96
|
-
if attrs.is_a?(Array)
|
97
|
-
attrs.map { |attr| create!(attr) }
|
98
|
-
else
|
99
|
-
build(attrs).tap(&:save!)
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
73
|
# Initialize a new object.
|
104
74
|
#
|
105
75
|
# @param [Hash] attrs Attributes with which to create the object.
|
@@ -145,161 +115,6 @@ module Dynamoid #:nodoc:
|
|
145
115
|
end
|
146
116
|
end
|
147
117
|
|
148
|
-
# Update document with provided values.
|
149
|
-
# Instantiates document and saves changes. Runs validations and callbacks.
|
150
|
-
#
|
151
|
-
# @param [Scalar value] partition key
|
152
|
-
# @param [Scalar value] sort key, optional
|
153
|
-
# @param [Hash] attributes
|
154
|
-
#
|
155
|
-
# @return [Dynamoid::Doument] updated document
|
156
|
-
#
|
157
|
-
# @example Update document
|
158
|
-
# Post.update(101, read: true)
|
159
|
-
def update(hash_key, range_key_value = nil, attrs)
|
160
|
-
model = find(hash_key, range_key: range_key_value, consistent_read: true)
|
161
|
-
model.update_attributes(attrs)
|
162
|
-
model
|
163
|
-
end
|
164
|
-
|
165
|
-
# Update document.
|
166
|
-
# Uses efficient low-level `UpdateItem` API call.
|
167
|
-
# Changes attibutes and loads new document version with one API call.
|
168
|
-
# Doesn't run validations and callbacks. Can make conditional update.
|
169
|
-
# If a document doesn't exist or specified conditions failed - returns `nil`
|
170
|
-
#
|
171
|
-
# @param [Scalar value] partition key
|
172
|
-
# @param [Scalar value] sort key (optional)
|
173
|
-
# @param [Hash] attributes
|
174
|
-
# @param [Hash] conditions
|
175
|
-
#
|
176
|
-
# @return [Dynamoid::Document/nil] updated document
|
177
|
-
#
|
178
|
-
# @example Update document
|
179
|
-
# Post.update_fields(101, read: true)
|
180
|
-
#
|
181
|
-
# @example Update document with condition
|
182
|
-
# Post.update_fields(101, { read: true }, if: { version: 1 })
|
183
|
-
def update_fields(hash_key_value, range_key_value = nil, attrs = {}, conditions = {})
|
184
|
-
optional_params = [range_key_value, attrs, conditions].compact
|
185
|
-
if optional_params.first.is_a?(Hash)
|
186
|
-
range_key_value = nil
|
187
|
-
attrs, conditions = optional_params[0..1]
|
188
|
-
else
|
189
|
-
range_key_value = optional_params.first
|
190
|
-
attrs, conditions = optional_params[1..2]
|
191
|
-
end
|
192
|
-
|
193
|
-
options = if range_key
|
194
|
-
value_casted = TypeCasting.cast_field(range_key_value, attributes[range_key])
|
195
|
-
value_dumped = Dumping.dump_field(value_casted, attributes[range_key])
|
196
|
-
{ range_key: value_dumped }
|
197
|
-
else
|
198
|
-
{}
|
199
|
-
end
|
200
|
-
|
201
|
-
(conditions[:if_exists] ||= {})[hash_key] = hash_key_value
|
202
|
-
options[:conditions] = conditions
|
203
|
-
|
204
|
-
attrs = attrs.symbolize_keys
|
205
|
-
if Dynamoid::Config.timestamps
|
206
|
-
attrs[:updated_at] ||= DateTime.now.in_time_zone(Time.zone)
|
207
|
-
end
|
208
|
-
|
209
|
-
begin
|
210
|
-
new_attrs = Dynamoid.adapter.update_item(table_name, hash_key_value, options) do |t|
|
211
|
-
attrs.each do |k, v|
|
212
|
-
value_casted = TypeCasting.cast_field(v, attributes[k])
|
213
|
-
value_dumped = Dumping.dump_field(value_casted, attributes[k])
|
214
|
-
t.set(k => value_dumped)
|
215
|
-
end
|
216
|
-
end
|
217
|
-
attrs_undumped = Undumping.undump_attributes(new_attrs, attributes)
|
218
|
-
new(attrs_undumped)
|
219
|
-
rescue Dynamoid::Errors::ConditionalCheckFailedException
|
220
|
-
end
|
221
|
-
end
|
222
|
-
|
223
|
-
# Update existing document or create new one.
|
224
|
-
# Similar to `.update_fields`. The only diffirence is creating new document.
|
225
|
-
#
|
226
|
-
# Uses efficient low-level `UpdateItem` API call.
|
227
|
-
# Changes attibutes and loads new document version with one API call.
|
228
|
-
# Doesn't run validations and callbacks. Can make conditional update.
|
229
|
-
# If specified conditions failed - returns `nil`
|
230
|
-
#
|
231
|
-
# @param [Scalar value] partition key
|
232
|
-
# @param [Scalar value] sort key (optional)
|
233
|
-
# @param [Hash] attributes
|
234
|
-
# @param [Hash] conditions
|
235
|
-
#
|
236
|
-
# @return [Dynamoid::Document/nil] updated document
|
237
|
-
#
|
238
|
-
# @example Update document
|
239
|
-
# Post.update(101, read: true)
|
240
|
-
#
|
241
|
-
# @example Update document
|
242
|
-
# Post.upsert(101, read: true)
|
243
|
-
def upsert(hash_key_value, range_key_value = nil, attrs = {}, conditions = {})
|
244
|
-
optional_params = [range_key_value, attrs, conditions].compact
|
245
|
-
if optional_params.first.is_a?(Hash)
|
246
|
-
range_key_value = nil
|
247
|
-
attrs, conditions = optional_params[0..1]
|
248
|
-
else
|
249
|
-
range_key_value = optional_params.first
|
250
|
-
attrs, conditions = optional_params[1..2]
|
251
|
-
end
|
252
|
-
|
253
|
-
options = if range_key
|
254
|
-
value_casted = TypeCasting.cast_field(range_key_value, attributes[range_key])
|
255
|
-
value_dumped = Dumping.dump_field(value_casted, attributes[range_key])
|
256
|
-
{ range_key: value_dumped }
|
257
|
-
else
|
258
|
-
{}
|
259
|
-
end
|
260
|
-
|
261
|
-
options[:conditions] = conditions
|
262
|
-
|
263
|
-
attrs = attrs.symbolize_keys
|
264
|
-
if Dynamoid::Config.timestamps
|
265
|
-
attrs[:updated_at] ||= DateTime.now.in_time_zone(Time.zone)
|
266
|
-
end
|
267
|
-
|
268
|
-
begin
|
269
|
-
new_attrs = Dynamoid.adapter.update_item(table_name, hash_key_value, options) do |t|
|
270
|
-
attrs.each do |k, v|
|
271
|
-
value_casted = TypeCasting.cast_field(v, attributes[k])
|
272
|
-
value_dumped = Dumping.dump_field(value_casted, attributes[k])
|
273
|
-
|
274
|
-
t.set(k => value_dumped)
|
275
|
-
end
|
276
|
-
end
|
277
|
-
|
278
|
-
attrs_undumped = Undumping.undump_attributes(new_attrs, attributes)
|
279
|
-
new(attrs_undumped)
|
280
|
-
rescue Dynamoid::Errors::ConditionalCheckFailedException
|
281
|
-
end
|
282
|
-
end
|
283
|
-
|
284
|
-
def inc(hash_key_value, range_key_value = nil, counters)
|
285
|
-
options = if range_key
|
286
|
-
value_casted = TypeCasting.cast_field(range_key_value, attributes[range_key])
|
287
|
-
value_dumped = Dumping.dump_field(value_casted, attributes[range_key])
|
288
|
-
{ range_key: value_dumped }
|
289
|
-
else
|
290
|
-
{}
|
291
|
-
end
|
292
|
-
|
293
|
-
Dynamoid.adapter.update_item(table_name, hash_key_value, options) do |t|
|
294
|
-
counters.each do |k, v|
|
295
|
-
value_casted = TypeCasting.cast_field(v, attributes[k])
|
296
|
-
value_dumped = Dumping.dump_field(value_casted, attributes[k])
|
297
|
-
|
298
|
-
t.add(k => value_dumped)
|
299
|
-
end
|
300
|
-
end
|
301
|
-
end
|
302
|
-
|
303
118
|
def deep_subclasses
|
304
119
|
subclasses + subclasses.map(&:deep_subclasses).flatten
|
305
120
|
end
|
@@ -323,13 +138,14 @@ module Dynamoid #:nodoc:
|
|
323
138
|
@associations ||= {}
|
324
139
|
@attributes_before_type_cast ||= {}
|
325
140
|
|
326
|
-
attrs_with_defaults = {}
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
141
|
+
attrs_with_defaults = self.class.attributes.reduce({}) do |res, (attribute, options)|
|
142
|
+
if attrs.key?(attribute)
|
143
|
+
res.merge(attribute => attrs[attribute])
|
144
|
+
elsif options.key?(:default)
|
145
|
+
res.merge(attribute => evaluate_default_value(options[:default]))
|
146
|
+
else
|
147
|
+
res
|
148
|
+
end
|
333
149
|
end
|
334
150
|
|
335
151
|
attrs_virtual = attrs.slice(*(attrs.keys - self.class.attributes.keys))
|
@@ -338,12 +154,6 @@ module Dynamoid #:nodoc:
|
|
338
154
|
end
|
339
155
|
end
|
340
156
|
|
341
|
-
def load(attrs)
|
342
|
-
attrs.each do |key, value|
|
343
|
-
send("#{key}=", value) if respond_to?("#{key}=")
|
344
|
-
end
|
345
|
-
end
|
346
|
-
|
347
157
|
# An object is equal to another object if their ids are equal.
|
348
158
|
#
|
349
159
|
# @since 0.2.0
|
@@ -365,24 +175,6 @@ module Dynamoid #:nodoc:
|
|
365
175
|
hash_key.hash ^ range_value.hash
|
366
176
|
end
|
367
177
|
|
368
|
-
# Reload an object from the database -- if you suspect the object has changed in the datastore and you need those
|
369
|
-
# changes to be reflected immediately, you would call this method. This is a consistent read.
|
370
|
-
#
|
371
|
-
# @return [Dynamoid::Document] the document this method was called on
|
372
|
-
#
|
373
|
-
# @since 0.2.0
|
374
|
-
def reload
|
375
|
-
options = { consistent_read: true }
|
376
|
-
|
377
|
-
if self.class.range_key
|
378
|
-
options[:range_key] = range_value
|
379
|
-
end
|
380
|
-
|
381
|
-
self.attributes = self.class.find(hash_key, options).attributes
|
382
|
-
@associations.values.each(&:reset)
|
383
|
-
self
|
384
|
-
end
|
385
|
-
|
386
178
|
# Return an object's hash key, regardless of what it might be called to the object.
|
387
179
|
#
|
388
180
|
# @since 0.4.0
|
data/lib/dynamoid/fields.rb
CHANGED
@@ -52,6 +52,8 @@ module Dynamoid #:nodoc:
|
|
52
52
|
end
|
53
53
|
self.attributes = attributes.merge(name => { type: type }.merge(options))
|
54
54
|
|
55
|
+
define_attribute_method(name) # Dirty API
|
56
|
+
|
55
57
|
generated_methods.module_eval do
|
56
58
|
define_method(named) { read_attribute(named) }
|
57
59
|
define_method("#{named}?") do
|
@@ -85,6 +87,10 @@ module Dynamoid #:nodoc:
|
|
85
87
|
field = field.to_sym
|
86
88
|
attributes.delete(field) || raise('No such field')
|
87
89
|
|
90
|
+
# Dirty API
|
91
|
+
undefine_attribute_methods
|
92
|
+
define_attribute_methods attributes.keys
|
93
|
+
|
88
94
|
generated_methods.module_eval do
|
89
95
|
remove_method field
|
90
96
|
remove_method :"#{field}="
|
@@ -121,6 +127,8 @@ module Dynamoid #:nodoc:
|
|
121
127
|
association.reset
|
122
128
|
end
|
123
129
|
|
130
|
+
attribute_will_change!(name) # Dirty API
|
131
|
+
|
124
132
|
@attributes_before_type_cast[name] = value
|
125
133
|
|
126
134
|
value_casted = TypeCasting.cast_field(value, self.class.attributes[name])
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dynamoid
|
4
|
+
module Loadable
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
def load(attrs)
|
8
|
+
attrs.each do |key, value|
|
9
|
+
send("#{key}=", value) if respond_to?("#{key}=")
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# Reload an object from the database -- if you suspect the object has changed in the datastore and you need those
|
14
|
+
# changes to be reflected immediately, you would call this method. This is a consistent read.
|
15
|
+
#
|
16
|
+
# @return [Dynamoid::Document] the document this method was called on
|
17
|
+
#
|
18
|
+
# @since 0.2.0
|
19
|
+
def reload
|
20
|
+
options = { consistent_read: true }
|
21
|
+
|
22
|
+
if self.class.range_key
|
23
|
+
options[:range_key] = range_value
|
24
|
+
end
|
25
|
+
|
26
|
+
self.attributes = self.class.find(hash_key, options).attributes
|
27
|
+
@associations.values.each(&:reset)
|
28
|
+
self
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/dynamoid/persistence.rb
CHANGED
@@ -4,6 +4,11 @@ require 'bigdecimal'
|
|
4
4
|
require 'securerandom'
|
5
5
|
require 'yaml'
|
6
6
|
|
7
|
+
require 'dynamoid/persistence/import'
|
8
|
+
require 'dynamoid/persistence/update_fields'
|
9
|
+
require 'dynamoid/persistence/upsert'
|
10
|
+
require 'dynamoid/persistence/save'
|
11
|
+
|
7
12
|
# encoding: utf-8
|
8
13
|
module Dynamoid
|
9
14
|
# Persistence is responsible for dumping objects to and marshalling objects from the datastore. It tries to reserialize
|
@@ -23,7 +28,7 @@ module Dynamoid
|
|
23
28
|
@table_name ||= [Dynamoid::Config.namespace.to_s, table_base_name].reject(&:empty?).join('_')
|
24
29
|
end
|
25
30
|
|
26
|
-
#
|
31
|
+
# Create a table.
|
27
32
|
#
|
28
33
|
# @param [Hash] options options to pass for table creation
|
29
34
|
# @option options [Symbol] :id the id field for the table
|
@@ -58,63 +63,185 @@ module Dynamoid
|
|
58
63
|
end
|
59
64
|
|
60
65
|
def from_database(attrs = {})
|
61
|
-
|
62
|
-
attrs_undumped = Undumping.undump_attributes(attrs,
|
63
|
-
|
66
|
+
klass = choose_right_class(attrs)
|
67
|
+
attrs_undumped = Undumping.undump_attributes(attrs, klass.attributes)
|
68
|
+
klass.new(attrs_undumped).tap { |r| r.new_record = false }
|
64
69
|
end
|
65
70
|
|
66
|
-
#
|
67
|
-
# Neither callbacks nor validations run.
|
68
|
-
# It works efficiently because of using BatchWriteItem.
|
69
|
-
#
|
70
|
-
# Returns array of models
|
71
|
+
# Create several models at once.
|
71
72
|
#
|
73
|
+
# Neither callbacks nor validations run.
|
74
|
+
# It works efficiently because of using `BatchWriteItem` API call.
|
75
|
+
# Return array of models.
|
72
76
|
# Uses backoff specified by `Dynamoid::Config.backoff` config option
|
73
77
|
#
|
74
|
-
# @param [Array<Hash>]
|
78
|
+
# @param [Array<Hash>] array_of_attributes
|
75
79
|
#
|
76
80
|
# @example
|
77
81
|
# User.import([{ name: 'a' }, { name: 'b' }])
|
78
|
-
def import(
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
if Dynamoid::Config.timestamps
|
83
|
-
time_now = DateTime.now.in_time_zone(Time.zone)
|
84
|
-
attrs[:created_at] ||= time_now
|
85
|
-
attrs[:updated_at] ||= time_now
|
86
|
-
end
|
82
|
+
def import(array_of_attributes)
|
83
|
+
Import.call(self, array_of_attributes)
|
84
|
+
end
|
87
85
|
|
88
|
-
|
89
|
-
|
90
|
-
|
86
|
+
# Create a model.
|
87
|
+
#
|
88
|
+
# Initializes a new object and immediately saves it to the database.
|
89
|
+
# Validates model and runs callbacks: before_create, before_save, after_save and after_create.
|
90
|
+
# Accepts both Hash and Array of Hashes and can create several models.
|
91
|
+
#
|
92
|
+
# @param [Hash|Array[Hash]] attrs Attributes with which to create the object.
|
93
|
+
#
|
94
|
+
# @return [Dynamoid::Document] the saved document
|
95
|
+
#
|
96
|
+
# @since 0.2.0
|
97
|
+
def create(attrs = {})
|
98
|
+
if attrs.is_a?(Array)
|
99
|
+
attrs.map { |attr| create(attr) }
|
100
|
+
else
|
101
|
+
build(attrs).tap(&:save)
|
91
102
|
end
|
103
|
+
end
|
92
104
|
|
93
|
-
|
94
|
-
|
105
|
+
# Create new model.
|
106
|
+
#
|
107
|
+
# Initializes a new object and immediately saves it to the database.
|
108
|
+
# Raises an exception if validation failed.
|
109
|
+
# Accepts both Hash and Array of Hashes and can create several models.
|
110
|
+
#
|
111
|
+
# @param [Hash|Array[Hash]] attrs Attributes with which to create the object.
|
112
|
+
#
|
113
|
+
# @return [Dynamoid::Document] the saved document
|
114
|
+
#
|
115
|
+
# @since 0.2.0
|
116
|
+
def create!(attrs = {})
|
117
|
+
if attrs.is_a?(Array)
|
118
|
+
attrs.map { |attr| create!(attr) }
|
119
|
+
else
|
120
|
+
build(attrs).tap(&:save!)
|
121
|
+
end
|
122
|
+
end
|
95
123
|
|
96
|
-
|
97
|
-
|
98
|
-
|
124
|
+
# Update document with provided attributes.
|
125
|
+
#
|
126
|
+
# Instantiates document and saves changes.
|
127
|
+
# Runs validations and callbacks.
|
128
|
+
#
|
129
|
+
# @param [Scalar value] partition key
|
130
|
+
# @param [Scalar value] sort key, optional
|
131
|
+
# @param [Hash] attributes
|
132
|
+
#
|
133
|
+
# @return [Dynamoid::Doument] updated document
|
134
|
+
#
|
135
|
+
# @example Update document
|
136
|
+
# Post.update(101, title: 'New title')
|
137
|
+
def update(hash_key, range_key_value = nil, attrs)
|
138
|
+
model = find(hash_key, range_key: range_key_value, consistent_read: true)
|
139
|
+
model.update_attributes(attrs)
|
140
|
+
model
|
141
|
+
end
|
99
142
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
143
|
+
# Update document.
|
144
|
+
#
|
145
|
+
# Uses efficient low-level `UpdateItem` API call.
|
146
|
+
# Changes attibutes and loads new document version with one API call.
|
147
|
+
# Doesn't run validations and callbacks. Can make conditional update.
|
148
|
+
# If a document doesn't exist or specified conditions failed - returns `nil`
|
149
|
+
#
|
150
|
+
# @param [Scalar value] partition key
|
151
|
+
# @param [Scalar value] sort key (optional)
|
152
|
+
# @param [Hash] attributes
|
153
|
+
# @param [Hash] conditions
|
154
|
+
#
|
155
|
+
# @return [Dynamoid::Document|nil] updated document
|
156
|
+
#
|
157
|
+
# @example Update document
|
158
|
+
# Post.update_fields(101, read: true)
|
159
|
+
#
|
160
|
+
# @example Update document with condition
|
161
|
+
# Post.update_fields(101, { title: 'New title' }, if: { version: 1 })
|
162
|
+
def update_fields(hash_key_value, range_key_value = nil, attrs = {}, conditions = {})
|
163
|
+
optional_params = [range_key_value, attrs, conditions].compact
|
164
|
+
if optional_params.first.is_a?(Hash)
|
165
|
+
range_key_value = nil
|
166
|
+
attrs, conditions = optional_params[0..1]
|
108
167
|
else
|
109
|
-
|
110
|
-
|
111
|
-
|
168
|
+
range_key_value = optional_params.first
|
169
|
+
attrs, conditions = optional_params[1..2]
|
170
|
+
end
|
112
171
|
|
113
|
-
|
172
|
+
UpdateFields.call(self,
|
173
|
+
partition_key: hash_key_value,
|
174
|
+
sort_key: range_key_value,
|
175
|
+
attributes: attrs,
|
176
|
+
conditions: conditions)
|
177
|
+
end
|
178
|
+
|
179
|
+
# Update existing document or create new one.
|
180
|
+
#
|
181
|
+
# Similar to `.update_fields`.
|
182
|
+
# The only diffirence is - it creates new document in case the document doesn't exist.
|
183
|
+
#
|
184
|
+
# Uses efficient low-level `UpdateItem` API call.
|
185
|
+
# Changes attibutes and loads new document version with one API call.
|
186
|
+
# Doesn't run validations and callbacks. Can make conditional update.
|
187
|
+
# If specified conditions failed - returns `nil`.
|
188
|
+
#
|
189
|
+
# @param [Scalar value] partition key
|
190
|
+
# @param [Scalar value] sort key (optional)
|
191
|
+
# @param [Hash] attributes
|
192
|
+
# @param [Hash] conditions
|
193
|
+
#
|
194
|
+
# @return [Dynamoid::Document/nil] updated document
|
195
|
+
#
|
196
|
+
# @example Update document
|
197
|
+
# Post.upsert(101, title: 'New title')
|
198
|
+
def upsert(hash_key_value, range_key_value = nil, attrs = {}, conditions = {})
|
199
|
+
optional_params = [range_key_value, attrs, conditions].compact
|
200
|
+
if optional_params.first.is_a?(Hash)
|
201
|
+
range_key_value = nil
|
202
|
+
attrs, conditions = optional_params[0..1]
|
203
|
+
else
|
204
|
+
range_key_value = optional_params.first
|
205
|
+
attrs, conditions = optional_params[1..2]
|
114
206
|
end
|
115
207
|
|
116
|
-
|
117
|
-
|
208
|
+
Upsert.call(self,
|
209
|
+
partition_key: hash_key_value,
|
210
|
+
sort_key: range_key_value,
|
211
|
+
attributes: attrs,
|
212
|
+
conditions: conditions)
|
213
|
+
end
|
214
|
+
|
215
|
+
# Increase numeric field by specified value.
|
216
|
+
#
|
217
|
+
# Can update several fields at once.
|
218
|
+
# Uses efficient low-level `UpdateItem` API call.
|
219
|
+
#
|
220
|
+
# @param [Scalar value] hash_key_value partition key
|
221
|
+
# @param [Scalar value] range_key_value sort key (optional)
|
222
|
+
# @param [Hash] counters value to increase by
|
223
|
+
#
|
224
|
+
# @return [Dynamoid::Document/nil] updated document
|
225
|
+
#
|
226
|
+
# @example Update document
|
227
|
+
# Post.inc(101, views_counter: 2, downloads: 10)
|
228
|
+
def inc(hash_key_value, range_key_value = nil, counters)
|
229
|
+
options = if range_key
|
230
|
+
value_casted = TypeCasting.cast_field(range_key_value, attributes[range_key])
|
231
|
+
value_dumped = Dumping.dump_field(value_casted, attributes[range_key])
|
232
|
+
{ range_key: value_dumped }
|
233
|
+
else
|
234
|
+
{}
|
235
|
+
end
|
236
|
+
|
237
|
+
Dynamoid.adapter.update_item(table_name, hash_key_value, options) do |t|
|
238
|
+
counters.each do |k, v|
|
239
|
+
value_casted = TypeCasting.cast_field(v, attributes[k])
|
240
|
+
value_dumped = Dumping.dump_field(value_casted, attributes[k])
|
241
|
+
|
242
|
+
t.add(k => value_dumped)
|
243
|
+
end
|
244
|
+
end
|
118
245
|
end
|
119
246
|
end
|
120
247
|
|
@@ -141,12 +268,15 @@ module Dynamoid
|
|
141
268
|
self.class.create_table
|
142
269
|
|
143
270
|
if new_record?
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
271
|
+
run_callbacks(:create) do
|
272
|
+
run_callbacks(:save) do
|
273
|
+
Save.call(self)
|
274
|
+
end
|
275
|
+
end
|
148
276
|
else
|
149
|
-
|
277
|
+
run_callbacks(:save) do
|
278
|
+
Save.call(self)
|
279
|
+
end
|
150
280
|
end
|
151
281
|
end
|
152
282
|
|
@@ -206,6 +336,7 @@ module Dynamoid
|
|
206
336
|
raise Dynamoid::Errors::StaleObjectError.new(self, 'update')
|
207
337
|
end
|
208
338
|
end
|
339
|
+
|
209
340
|
end
|
210
341
|
|
211
342
|
def update(conditions = {}, &block)
|
@@ -280,44 +411,5 @@ module Dynamoid
|
|
280
411
|
rescue Dynamoid::Errors::ConditionalCheckFailedException
|
281
412
|
raise Dynamoid::Errors::StaleObjectError.new(self, 'delete')
|
282
413
|
end
|
283
|
-
|
284
|
-
private
|
285
|
-
|
286
|
-
# Persist the object into the datastore. Assign it an id first if it doesn't have one.
|
287
|
-
#
|
288
|
-
# @since 0.2.0
|
289
|
-
def persist(conditions = nil)
|
290
|
-
run_callbacks(:save) do
|
291
|
-
self.hash_key = SecureRandom.uuid if hash_key.blank?
|
292
|
-
|
293
|
-
# Add an exists check to prevent overwriting existing records with new ones
|
294
|
-
if new_record?
|
295
|
-
conditions ||= {}
|
296
|
-
(conditions[:unless_exists] ||= []) << self.class.hash_key
|
297
|
-
end
|
298
|
-
|
299
|
-
# Add an optimistic locking check if the lock_version column exists
|
300
|
-
if self.class.attributes[:lock_version]
|
301
|
-
conditions ||= {}
|
302
|
-
self.lock_version = (lock_version || 0) + 1
|
303
|
-
# Uses the original lock_version value from ActiveModel::Dirty in case user changed lock_version manually
|
304
|
-
(conditions[:if] ||= {})[:lock_version] = changes[:lock_version][0] if changes[:lock_version][0]
|
305
|
-
end
|
306
|
-
|
307
|
-
attributes_dumped = Dumping.dump_attributes(attributes, self.class.attributes)
|
308
|
-
|
309
|
-
begin
|
310
|
-
Dynamoid.adapter.write(self.class.table_name, attributes_dumped, conditions)
|
311
|
-
@new_record = false
|
312
|
-
true
|
313
|
-
rescue Dynamoid::Errors::ConditionalCheckFailedException => e
|
314
|
-
if new_record?
|
315
|
-
raise Dynamoid::Errors::RecordNotUnique.new(e, self)
|
316
|
-
else
|
317
|
-
raise Dynamoid::Errors::StaleObjectError.new(self, 'persist')
|
318
|
-
end
|
319
|
-
end
|
320
|
-
end
|
321
|
-
end
|
322
414
|
end
|
323
415
|
end
|