dynamoid 3.9.0 → 3.11.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 +26 -6
- data/README.md +202 -25
- data/dynamoid.gemspec +5 -6
- data/lib/dynamoid/adapter.rb +19 -13
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/create_table.rb +2 -2
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/filter_expression_convertor.rb +113 -0
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/item_updater.rb +21 -2
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/projection_expression_convertor.rb +40 -0
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/query.rb +46 -61
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/scan.rb +34 -28
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/transact.rb +31 -0
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3.rb +95 -66
- data/lib/dynamoid/associations/belongs_to.rb +6 -6
- data/lib/dynamoid/associations.rb +1 -1
- data/lib/dynamoid/components.rb +1 -0
- data/lib/dynamoid/config/options.rb +12 -12
- data/lib/dynamoid/config.rb +3 -0
- data/lib/dynamoid/criteria/chain.rb +149 -142
- data/lib/dynamoid/criteria/key_fields_detector.rb +6 -7
- data/lib/dynamoid/criteria/nonexistent_fields_detector.rb +2 -2
- data/lib/dynamoid/criteria/where_conditions.rb +36 -0
- data/lib/dynamoid/dirty.rb +87 -12
- data/lib/dynamoid/document.rb +1 -1
- data/lib/dynamoid/dumping.rb +38 -16
- data/lib/dynamoid/errors.rb +14 -2
- data/lib/dynamoid/fields/declare.rb +6 -6
- data/lib/dynamoid/fields.rb +6 -8
- data/lib/dynamoid/finders.rb +23 -32
- data/lib/dynamoid/indexes.rb +6 -7
- data/lib/dynamoid/loadable.rb +3 -2
- data/lib/dynamoid/persistence/inc.rb +6 -7
- 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 +17 -18
- data/lib/dynamoid/persistence/update_fields.rb +7 -5
- data/lib/dynamoid/persistence/update_validations.rb +1 -1
- data/lib/dynamoid/persistence/upsert.rb +5 -4
- data/lib/dynamoid/persistence.rb +77 -21
- 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 +60 -0
- data/lib/dynamoid/transaction_write/delete_with_primary_key.rb +59 -0
- data/lib/dynamoid/transaction_write/destroy.rb +79 -0
- data/lib/dynamoid/transaction_write/save.rb +164 -0
- data/lib/dynamoid/transaction_write/update_attributes.rb +46 -0
- data/lib/dynamoid/transaction_write/update_fields.rb +102 -0
- data/lib/dynamoid/transaction_write/upsert.rb +96 -0
- data/lib/dynamoid/transaction_write.rb +464 -0
- data/lib/dynamoid/type_casting.rb +18 -15
- data/lib/dynamoid/undumping.rb +14 -3
- data/lib/dynamoid/validations.rb +1 -1
- data/lib/dynamoid/version.rb +1 -1
- data/lib/dynamoid.rb +7 -0
- metadata +30 -16
- data/lib/dynamoid/criteria/ignored_conditions_detector.rb +0 -41
- data/lib/dynamoid/criteria/overwritten_conditions_detector.rb +0 -40
@@ -0,0 +1,464 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'dynamoid/transaction_write/create'
|
4
|
+
require 'dynamoid/transaction_write/delete_with_primary_key'
|
5
|
+
require 'dynamoid/transaction_write/delete_with_instance'
|
6
|
+
require 'dynamoid/transaction_write/destroy'
|
7
|
+
require 'dynamoid/transaction_write/save'
|
8
|
+
require 'dynamoid/transaction_write/update_fields'
|
9
|
+
require 'dynamoid/transaction_write/update_attributes'
|
10
|
+
require 'dynamoid/transaction_write/upsert'
|
11
|
+
|
12
|
+
module Dynamoid
|
13
|
+
# The class +TransactionWrite+ provides means to perform multiple modifying
|
14
|
+
# operations in transaction, that is atomically, so that either all of them
|
15
|
+
# succeed, or all of them fail.
|
16
|
+
#
|
17
|
+
# The persisting methods are supposed to be as close as possible to their
|
18
|
+
# non-transactional counterparts like +.create+, +#save+ and +#delete+:
|
19
|
+
#
|
20
|
+
# user = User.new()
|
21
|
+
# payment = Payment.find(1)
|
22
|
+
#
|
23
|
+
# Dynamoid::TransactionWrite.execute do |t|
|
24
|
+
# t.save! user
|
25
|
+
# t.create! Account, name: 'A'
|
26
|
+
# t.delete payment
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# The only difference is that the methods are called on a transaction
|
30
|
+
# instance and a model or a model class should be specified.
|
31
|
+
#
|
32
|
+
# So +user.save!+ becomes +t.save!(user)+, +Account.create!(name: 'A')+
|
33
|
+
# becomes +t.create!(Account, name: 'A')+, and +payment.delete+ becomes
|
34
|
+
# +t.delete(payment)+.
|
35
|
+
#
|
36
|
+
# A transaction can be used without a block. This way a transaction instance
|
37
|
+
# should be instantiated and committed manually with +#commit+ method:
|
38
|
+
#
|
39
|
+
# t = Dynamoid::TransactionWrite.new
|
40
|
+
#
|
41
|
+
# t.save! user
|
42
|
+
# t.create! Account, name: 'A'
|
43
|
+
# t.delete payment
|
44
|
+
#
|
45
|
+
# t.commit
|
46
|
+
#
|
47
|
+
# Some persisting methods are intentionally not available in a transaction,
|
48
|
+
# e.g. +.update+ and +.update!+ that simply call +.find+ and
|
49
|
+
# +#update_attributes+ methods. These methods perform multiple operations so
|
50
|
+
# cannot be implemented in a transactional atomic way.
|
51
|
+
#
|
52
|
+
#
|
53
|
+
# ### DynamoDB's transactions
|
54
|
+
#
|
55
|
+
# The main difference between DynamoDB transactions and a common interface is
|
56
|
+
# that DynamoDB's transactions are executed in batch. So in Dynamoid no
|
57
|
+
# changes are actually persisted when some transactional method (e.g+ `#save+) is
|
58
|
+
# called. All the changes are persisted at the end.
|
59
|
+
#
|
60
|
+
# A +TransactWriteItems+ DynamoDB operation is used (see
|
61
|
+
# [documentation](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_TransactWriteItems.html)
|
62
|
+
# for details).
|
63
|
+
#
|
64
|
+
#
|
65
|
+
# ### Callbacks
|
66
|
+
#
|
67
|
+
# The transactional methods support +before_+, +after_+ and +around_+
|
68
|
+
# callbacks to the extend the non-transactional methods support them.
|
69
|
+
#
|
70
|
+
# There is important difference - a transactional method runs callbacks
|
71
|
+
# immediately (even +after_+ ones) when it is called before changes are
|
72
|
+
# actually persisted. So code in +after_+ callbacks does not see observes
|
73
|
+
# them in DynamoDB and so for.
|
74
|
+
#
|
75
|
+
# When a callback aborts persisting of a model or a model is invalid then
|
76
|
+
# transaction is not aborted and may commit successfully.
|
77
|
+
#
|
78
|
+
#
|
79
|
+
# ### Transaction rollback
|
80
|
+
#
|
81
|
+
# A transaction is rolled back on DynamoDB's side automatically when:
|
82
|
+
# - an ongoing operation is in the process of updating the same item.
|
83
|
+
# - there is insufficient provisioned capacity for the transaction to be completed.
|
84
|
+
# - an item size becomes too large (bigger than 400 KB), a local secondary index (LSI) becomes too large, or a similar validation error occurs because of changes made by the transaction.
|
85
|
+
# - the aggregate size of the items in the transaction exceeds 4 MB.
|
86
|
+
# - there is a user error, such as an invalid data format.
|
87
|
+
#
|
88
|
+
# A transaction can be interrupted simply by an exception raised within a
|
89
|
+
# block. As far as no changes are actually persisted before the +#commit+
|
90
|
+
# method call - there is nothing to undo on the DynamoDB's site.
|
91
|
+
#
|
92
|
+
# Raising +Dynamoid::Errors::Rollback+ exception leads to interrupting a
|
93
|
+
# transation and it isn't propogated:
|
94
|
+
#
|
95
|
+
# Dynamoid::TransactionWrite.execute do |t|
|
96
|
+
# t.save! user
|
97
|
+
# t.create! Account, name: 'A'
|
98
|
+
#
|
99
|
+
# if user.is_admin?
|
100
|
+
# raise Dynamoid::Errors::Rollback
|
101
|
+
# end
|
102
|
+
# end
|
103
|
+
#
|
104
|
+
# When a transaction is successfully committed or rolled backed -
|
105
|
+
# corresponding +#after_commit+ or +#after_rollback+ callbacks are run for
|
106
|
+
# each involved model.
|
107
|
+
class TransactionWrite
|
108
|
+
def self.execute
|
109
|
+
transaction = new
|
110
|
+
|
111
|
+
begin
|
112
|
+
yield transaction
|
113
|
+
rescue StandardError => e
|
114
|
+
transaction.rollback
|
115
|
+
|
116
|
+
unless e.is_a?(Dynamoid::Errors::Rollback)
|
117
|
+
raise e
|
118
|
+
end
|
119
|
+
else
|
120
|
+
transaction.commit
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def initialize
|
125
|
+
@actions = []
|
126
|
+
end
|
127
|
+
|
128
|
+
# Persist all the changes.
|
129
|
+
#
|
130
|
+
# transaction = Dynamoid::TransactionWrite.new
|
131
|
+
# # ...
|
132
|
+
# transaction.commit
|
133
|
+
def commit
|
134
|
+
actions_to_commit = @actions.reject(&:aborted?).reject(&:skipped?)
|
135
|
+
return if actions_to_commit.empty?
|
136
|
+
|
137
|
+
action_requests = actions_to_commit.map(&:action_request)
|
138
|
+
Dynamoid.adapter.transact_write_items(action_requests)
|
139
|
+
actions_to_commit.each(&:on_commit)
|
140
|
+
|
141
|
+
nil
|
142
|
+
rescue Aws::Errors::ServiceError
|
143
|
+
run_on_rollback_callbacks
|
144
|
+
raise
|
145
|
+
end
|
146
|
+
|
147
|
+
def rollback
|
148
|
+
run_on_rollback_callbacks
|
149
|
+
end
|
150
|
+
|
151
|
+
# Create new model or persist changes in already existing one.
|
152
|
+
#
|
153
|
+
# Run the validation and callbacks. Returns +true+ if saving is successful
|
154
|
+
# and +false+ otherwise.
|
155
|
+
#
|
156
|
+
# user = User.new
|
157
|
+
#
|
158
|
+
# Dynamoid::TransactionWrite.execute do |t|
|
159
|
+
# t.save!(user)
|
160
|
+
# end
|
161
|
+
#
|
162
|
+
# Validation can be skipped with +validate: false+ option:
|
163
|
+
#
|
164
|
+
# user = User.new(age: -1)
|
165
|
+
#
|
166
|
+
# Dynamoid::TransactionWrite.execute do |t|
|
167
|
+
# t.save!(user, validate: false)
|
168
|
+
# end
|
169
|
+
#
|
170
|
+
# +save!+ by default sets timestamps attributes - +created_at+ and
|
171
|
+
# +updated_at+ when creates new model and updates +updated_at+ attribute
|
172
|
+
# when updates already existing one.
|
173
|
+
#
|
174
|
+
# If a model is new and hash key (+id+ by default) is not assigned yet
|
175
|
+
# it was assigned implicitly with random UUID value.
|
176
|
+
#
|
177
|
+
# When a model is not persisted - its id should have unique value.
|
178
|
+
# Otherwise a transaction will be rolled back.
|
179
|
+
#
|
180
|
+
# @param model [Dynamoid::Document] a model
|
181
|
+
# @param options [Hash] (optional)
|
182
|
+
# @option options [true|false] :validate validate a model or not - +true+ by default (optional)
|
183
|
+
# @return [true|false] Whether saving successful or not
|
184
|
+
def save!(model, **options)
|
185
|
+
action = Dynamoid::TransactionWrite::Save.new(model, **options, raise_error: true)
|
186
|
+
register_action action
|
187
|
+
end
|
188
|
+
|
189
|
+
# Create new model or persist changes in already existing one.
|
190
|
+
#
|
191
|
+
# Run the validation and callbacks. Raise
|
192
|
+
# +Dynamoid::Errors::DocumentNotValid+ unless this object is valid.
|
193
|
+
#
|
194
|
+
# user = User.new
|
195
|
+
#
|
196
|
+
# Dynamoid::TransactionWrite.execute do |t|
|
197
|
+
# t.save(user)
|
198
|
+
# end
|
199
|
+
#
|
200
|
+
# Validation can be skipped with +validate: false+ option:
|
201
|
+
#
|
202
|
+
# user = User.new(age: -1)
|
203
|
+
#
|
204
|
+
# Dynamoid::TransactionWrite.execute do |t|
|
205
|
+
# t.save(user, validate: false)
|
206
|
+
# end
|
207
|
+
#
|
208
|
+
# +save+ by default sets timestamps attributes - +created_at+ and
|
209
|
+
# +updated_at+ when creates new model and updates +updated_at+ attribute
|
210
|
+
# when updates already existing one.
|
211
|
+
#
|
212
|
+
# If a model is new and hash key (+id+ by default) is not assigned yet
|
213
|
+
# it was assigned implicitly with random UUID value.
|
214
|
+
#
|
215
|
+
# When a model is not persisted - its id should have unique value.
|
216
|
+
# Otherwise a transaction will be rolled back.
|
217
|
+
#
|
218
|
+
# @param model [Dynamoid::Document] a model
|
219
|
+
# @param options [Hash] (optional)
|
220
|
+
# @option options [true|false] :validate validate a model or not - +true+ by default (optional)
|
221
|
+
# @return [true|false] Whether saving successful or not
|
222
|
+
def save(model, **options)
|
223
|
+
action = Dynamoid::TransactionWrite::Save.new(model, **options, raise_error: false)
|
224
|
+
register_action action
|
225
|
+
end
|
226
|
+
|
227
|
+
# Create a model.
|
228
|
+
#
|
229
|
+
# Dynamoid::TransactionWrite.execute do |t|
|
230
|
+
# t.create!(User, name: 'A')
|
231
|
+
# end
|
232
|
+
#
|
233
|
+
# Accepts both Hash and Array of Hashes and can create several models.
|
234
|
+
#
|
235
|
+
# Dynamoid::TransactionWrite.execute do |t|
|
236
|
+
# t.create!(User, [{name: 'A'}, {name: 'B'}, {name: 'C'}])
|
237
|
+
# end
|
238
|
+
#
|
239
|
+
# Instantiates a model and pass it into an optional block to set other
|
240
|
+
# attributes.
|
241
|
+
#
|
242
|
+
# Dynamoid::TransactionWrite.execute do |t|
|
243
|
+
# t.create!(User, name: 'A') do |user|
|
244
|
+
# user.initialize_roles
|
245
|
+
# end
|
246
|
+
# end
|
247
|
+
#
|
248
|
+
# Validates model and runs callbacks.
|
249
|
+
#
|
250
|
+
# @param model_class [Class] a model class which should be instantiated
|
251
|
+
# @param attributes [Hash|Array<Hash>] attributes of a model
|
252
|
+
# @param block [Proc] a block to process a model after initialization
|
253
|
+
# @return [Dynamoid::Document] a model that was instantiated but not yet persisted
|
254
|
+
def create!(model_class, attributes = {}, &block)
|
255
|
+
if attributes.is_a? Array
|
256
|
+
attributes.map do |attr|
|
257
|
+
action = Dynamoid::TransactionWrite::Create.new(model_class, attr, raise_error: true, &block)
|
258
|
+
register_action action
|
259
|
+
end
|
260
|
+
else
|
261
|
+
action = Dynamoid::TransactionWrite::Create.new(model_class, attributes, raise_error: true, &block)
|
262
|
+
register_action action
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
# Create a model.
|
267
|
+
#
|
268
|
+
# Dynamoid::TransactionWrite.execute do |t|
|
269
|
+
# t.create(User, name: 'A')
|
270
|
+
# end
|
271
|
+
#
|
272
|
+
# Accepts both Hash and Array of Hashes and can create several models.
|
273
|
+
#
|
274
|
+
# Dynamoid::TransactionWrite.execute do |t|
|
275
|
+
# t.create(User, [{name: 'A'}, {name: 'B'}, {name: 'C'}])
|
276
|
+
# end
|
277
|
+
#
|
278
|
+
# Instantiates a model and pass it into an optional block to set other
|
279
|
+
# attributes.
|
280
|
+
#
|
281
|
+
# Dynamoid::TransactionWrite.execute do |t|
|
282
|
+
# t.create(User, name: 'A') do |user|
|
283
|
+
# user.initialize_roles
|
284
|
+
# end
|
285
|
+
# end
|
286
|
+
#
|
287
|
+
# Validates model and runs callbacks.
|
288
|
+
#
|
289
|
+
# @param model_class [Class] a model class which should be instantiated
|
290
|
+
# @param attributes [Hash|Array<Hash>] attributes of a model
|
291
|
+
# @param block [Proc] a block to process a model after initialization
|
292
|
+
# @return [Dynamoid::Document] a model that was instantiated but not yet persisted
|
293
|
+
def create(model_class, attributes = {}, &block)
|
294
|
+
if attributes.is_a? Array
|
295
|
+
attributes.map do |attr|
|
296
|
+
action = Dynamoid::TransactionWrite::Create.new(model_class, attr, raise_error: false, &block)
|
297
|
+
register_action action
|
298
|
+
end
|
299
|
+
else
|
300
|
+
action = Dynamoid::TransactionWrite::Create.new(model_class, attributes, raise_error: false, &block)
|
301
|
+
register_action action
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
# Update an existing document or create a new one.
|
306
|
+
#
|
307
|
+
# If a document with specified hash and range keys doesn't exist it
|
308
|
+
# creates a new document with specified attributes. Doesn't run
|
309
|
+
# validations and callbacks.
|
310
|
+
#
|
311
|
+
# Dynamoid::TransactionWrite.execute do |t|
|
312
|
+
# t.upsert(User, '1', age: 26)
|
313
|
+
# end
|
314
|
+
#
|
315
|
+
# If range key is declared for a model it should be passed as well:
|
316
|
+
#
|
317
|
+
# Dynamoid::TransactionWrite.execute do |t|
|
318
|
+
# t.upsert(User, '1', 'Tylor', age: 26)
|
319
|
+
# end
|
320
|
+
#
|
321
|
+
# Raises a +Dynamoid::Errors::UnknownAttribute+ exception if any of the
|
322
|
+
# attributes is not declared in the model class.
|
323
|
+
#
|
324
|
+
# @param model_class [Class] a model class
|
325
|
+
# @param hash_key [Scalar value] hash key value
|
326
|
+
# @param range_key [Scalar value] range key value (optional)
|
327
|
+
# @param attributes [Hash]
|
328
|
+
# @return [nil]
|
329
|
+
def upsert(model_class, hash_key, range_key = nil, attributes) # rubocop:disable Style/OptionalArguments
|
330
|
+
action = Dynamoid::TransactionWrite::Upsert.new(model_class, hash_key, range_key, attributes)
|
331
|
+
register_action action
|
332
|
+
end
|
333
|
+
|
334
|
+
# Update document.
|
335
|
+
#
|
336
|
+
# Doesn't run validations and callbacks.
|
337
|
+
#
|
338
|
+
# Dynamoid::TransactionWrite.execute do |t|
|
339
|
+
# t.update_fields(User, '1', age: 26)
|
340
|
+
# end
|
341
|
+
#
|
342
|
+
# If range key is declared for a model it should be passed as well:
|
343
|
+
#
|
344
|
+
# Dynamoid::TransactionWrite.execute do |t|
|
345
|
+
# t.update_fields(User, '1', 'Tylor', age: 26)
|
346
|
+
# end
|
347
|
+
#
|
348
|
+
# Raises a +Dynamoid::Errors::UnknownAttribute+ exception if any of the
|
349
|
+
# attributes is not declared in the model class.
|
350
|
+
#
|
351
|
+
# @param model_class [Class] a model class
|
352
|
+
# @param hash_key [Scalar value] hash key value
|
353
|
+
# @param range_key [Scalar value] range key value (optional)
|
354
|
+
# @param attributes [Hash]
|
355
|
+
# @return [nil]
|
356
|
+
def update_fields(model_class, hash_key, range_key = nil, attributes) # rubocop:disable Style/OptionalArguments
|
357
|
+
action = Dynamoid::TransactionWrite::UpdateFields.new(model_class, hash_key, range_key, attributes)
|
358
|
+
register_action action
|
359
|
+
end
|
360
|
+
|
361
|
+
# Update multiple attributes at once.
|
362
|
+
#
|
363
|
+
# Dynamoid::TransactionWrite.execute do |t|
|
364
|
+
# t.update_attributes(user, age: 27, last_name: 'Tylor')
|
365
|
+
# end
|
366
|
+
#
|
367
|
+
# Returns +true+ if saving is successful and +false+
|
368
|
+
# otherwise.
|
369
|
+
#
|
370
|
+
# @param model [Dynamoid::Document] a model
|
371
|
+
# @param attributes [Hash] a hash of attributes to update
|
372
|
+
# @return [true|false] Whether updating successful or not
|
373
|
+
def update_attributes(model, attributes)
|
374
|
+
action = Dynamoid::TransactionWrite::UpdateAttributes.new(model, attributes, raise_error: false)
|
375
|
+
register_action action
|
376
|
+
end
|
377
|
+
|
378
|
+
# Update multiple attributes at once.
|
379
|
+
#
|
380
|
+
# Returns +true+ if saving is successful and +false+
|
381
|
+
# otherwise.
|
382
|
+
#
|
383
|
+
# Dynamoid::TransactionWrite.execute do |t|
|
384
|
+
# t.update_attributes(user, age: 27, last_name: 'Tylor')
|
385
|
+
# end
|
386
|
+
#
|
387
|
+
# Raises a +Dynamoid::Errors::DocumentNotValid+ exception if some vaidation
|
388
|
+
# fails.
|
389
|
+
#
|
390
|
+
# @param model [Dynamoid::Document] a model
|
391
|
+
# @param attributes [Hash] a hash of attributes to update
|
392
|
+
def update_attributes!(model, attributes)
|
393
|
+
action = Dynamoid::TransactionWrite::UpdateAttributes.new(model, attributes, raise_error: true)
|
394
|
+
register_action action
|
395
|
+
end
|
396
|
+
|
397
|
+
# Delete a model.
|
398
|
+
#
|
399
|
+
# Can be called either with a model:
|
400
|
+
#
|
401
|
+
# Dynamoid::TransactionWrite.execute do |t|
|
402
|
+
# t.delete(user)
|
403
|
+
# end
|
404
|
+
#
|
405
|
+
# or with a primary key:
|
406
|
+
#
|
407
|
+
# Dynamoid::TransactionWrite.execute do |t|
|
408
|
+
# t.delete(User, user_id)
|
409
|
+
# end
|
410
|
+
#
|
411
|
+
# Raise +MissingRangeKey+ if a range key is declared but not passed as argument.
|
412
|
+
#
|
413
|
+
# @param model_or_model_class [Class|Dynamoid::Document] either model or model class
|
414
|
+
# @param hash_key [Scalar value] hash key value
|
415
|
+
# @param range_key [Scalar value] range key value (optional)
|
416
|
+
# @return [Dynamoid::Document] self
|
417
|
+
def delete(model_or_model_class, hash_key = nil, range_key = nil)
|
418
|
+
action = if model_or_model_class.is_a? Class
|
419
|
+
Dynamoid::TransactionWrite::DeleteWithPrimaryKey.new(model_or_model_class, hash_key, range_key)
|
420
|
+
else
|
421
|
+
Dynamoid::TransactionWrite::DeleteWithInstance.new(model_or_model_class)
|
422
|
+
end
|
423
|
+
register_action action
|
424
|
+
end
|
425
|
+
|
426
|
+
# Delete a model.
|
427
|
+
#
|
428
|
+
# Runs callbacks.
|
429
|
+
#
|
430
|
+
# Raises +Dynamoid::Errors::RecordNotDestroyed+ exception if model deleting
|
431
|
+
# failed (e.g. aborted by a callback).
|
432
|
+
#
|
433
|
+
# @param model [Dynamoid::Document] a model
|
434
|
+
# @return [Dynamoid::Document|false] returns self if destoying is succefull, +false+ otherwise
|
435
|
+
def destroy!(model)
|
436
|
+
action = Dynamoid::TransactionWrite::Destroy.new(model, raise_error: true)
|
437
|
+
register_action action
|
438
|
+
end
|
439
|
+
|
440
|
+
# Delete a model.
|
441
|
+
#
|
442
|
+
# Runs callbacks.
|
443
|
+
#
|
444
|
+
# @param model [Dynamoid::Document] a model
|
445
|
+
# @return [Dynamoid::Document] self
|
446
|
+
def destroy(model)
|
447
|
+
action = Dynamoid::TransactionWrite::Destroy.new(model, raise_error: false)
|
448
|
+
register_action action
|
449
|
+
end
|
450
|
+
|
451
|
+
private
|
452
|
+
|
453
|
+
def register_action(action)
|
454
|
+
@actions << action
|
455
|
+
action.on_registration
|
456
|
+
action.observable_by_user_result
|
457
|
+
end
|
458
|
+
|
459
|
+
def run_on_rollback_callbacks
|
460
|
+
actions_to_commit = @actions.reject(&:aborted?).reject(&:skipped?)
|
461
|
+
actions_to_commit.each(&:on_rollback)
|
462
|
+
end
|
463
|
+
end
|
464
|
+
end
|
@@ -57,11 +57,12 @@ module Dynamoid
|
|
57
57
|
|
58
58
|
class StringTypeCaster < Base
|
59
59
|
def process(value)
|
60
|
-
|
60
|
+
case value
|
61
|
+
when true
|
61
62
|
't'
|
62
|
-
|
63
|
+
when false
|
63
64
|
'f'
|
64
|
-
|
65
|
+
when String
|
65
66
|
value.dup
|
66
67
|
else
|
67
68
|
value.to_s
|
@@ -71,6 +72,7 @@ module Dynamoid
|
|
71
72
|
|
72
73
|
class IntegerTypeCaster < Base
|
73
74
|
def process(value)
|
75
|
+
# rubocop:disable Lint/DuplicateBranch
|
74
76
|
if value == true
|
75
77
|
1
|
76
78
|
elsif value == false
|
@@ -84,11 +86,13 @@ module Dynamoid
|
|
84
86
|
else
|
85
87
|
value.to_i
|
86
88
|
end
|
89
|
+
# rubocop:enable Lint/DuplicateBranch
|
87
90
|
end
|
88
91
|
end
|
89
92
|
|
90
93
|
class NumberTypeCaster < Base
|
91
94
|
def process(value)
|
95
|
+
# rubocop:disable Lint/DuplicateBranch
|
92
96
|
if value == true
|
93
97
|
1
|
94
98
|
elsif value == false
|
@@ -104,6 +108,7 @@ module Dynamoid
|
|
104
108
|
else
|
105
109
|
value.to_d
|
106
110
|
end
|
111
|
+
# rubocop:enable Lint/DuplicateBranch
|
107
112
|
end
|
108
113
|
end
|
109
114
|
|
@@ -135,7 +140,7 @@ module Dynamoid
|
|
135
140
|
raise ArgumentError, "Set element type #{element_type} isn't supported"
|
136
141
|
end
|
137
142
|
|
138
|
-
set.
|
143
|
+
set.to_set { |el| type_caster.process(el) }
|
139
144
|
end
|
140
145
|
|
141
146
|
def element_type
|
@@ -227,10 +232,10 @@ module Dynamoid
|
|
227
232
|
nil
|
228
233
|
elsif value.is_a?(String)
|
229
234
|
dt = begin
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
235
|
+
DateTime.parse(value)
|
236
|
+
rescue StandardError
|
237
|
+
nil
|
238
|
+
end
|
234
239
|
if dt
|
235
240
|
seconds = string_utc_offset(value) || ApplicationTimeZone.utc_offset
|
236
241
|
offset = seconds_to_offset(seconds)
|
@@ -255,9 +260,7 @@ module Dynamoid
|
|
255
260
|
|
256
261
|
class DateTypeCaster < Base
|
257
262
|
def process(value)
|
258
|
-
if
|
259
|
-
nil
|
260
|
-
else
|
263
|
+
if value.respond_to?(:to_date)
|
261
264
|
begin
|
262
265
|
value.to_date
|
263
266
|
rescue StandardError
|
@@ -277,17 +280,17 @@ module Dynamoid
|
|
277
280
|
def process(value)
|
278
281
|
if value == ''
|
279
282
|
nil
|
280
|
-
elsif [false, 'false', 'FALSE', 0, '0', 'f', 'F', 'off', 'OFF'].include? value
|
281
|
-
false
|
282
283
|
else
|
283
|
-
|
284
|
+
![false, 'false', 'FALSE', 0, '0', 'f', 'F', 'off', 'OFF'].include? value
|
284
285
|
end
|
285
286
|
end
|
286
287
|
end
|
287
288
|
|
288
289
|
class BinaryTypeCaster < Base
|
289
290
|
def process(value)
|
290
|
-
if value.is_a?
|
291
|
+
if value.is_a?(StringIO) || value.is_a?(IO)
|
292
|
+
value
|
293
|
+
elsif value.is_a?(String)
|
291
294
|
value.dup
|
292
295
|
else
|
293
296
|
value.to_s
|
data/lib/dynamoid/undumping.rb
CHANGED
@@ -115,7 +115,7 @@ module Dynamoid
|
|
115
115
|
def process_typed_collection(set)
|
116
116
|
if allowed_type?
|
117
117
|
undumper = Undumping.find_undumper(element_options)
|
118
|
-
set.
|
118
|
+
set.to_set { |el| undumper.process(el) }
|
119
119
|
else
|
120
120
|
raise ArgumentError, "Set element type #{element_type} isn't supported"
|
121
121
|
end
|
@@ -238,7 +238,8 @@ module Dynamoid
|
|
238
238
|
class SerializedUndumper < Base
|
239
239
|
# We must use YAML.safe_load in Ruby 3.1 to handle serialized Set class
|
240
240
|
minimum_ruby_version = ->(version) { Gem::Version.new(RUBY_VERSION) >= Gem::Version.new(version) }
|
241
|
-
|
241
|
+
|
242
|
+
# Once we drop support for Rubies older than 2.6 we can remove this condition (with major version bump)!
|
242
243
|
# YAML_SAFE_LOAD = minimum_ruby_version.call("2.6")
|
243
244
|
# But we don't want to change behavior for Ruby <= 3.0 that has been using the gem, without a major version bump
|
244
245
|
YAML_SAFE_LOAD = minimum_ruby_version.call('3.1')
|
@@ -284,7 +285,17 @@ module Dynamoid
|
|
284
285
|
|
285
286
|
class BinaryUndumper < Base
|
286
287
|
def process(value)
|
287
|
-
|
288
|
+
store_as_binary = if @options[:store_as_native_binary].nil?
|
289
|
+
Dynamoid.config.store_binary_as_native
|
290
|
+
else
|
291
|
+
@options[:store_as_native_binary]
|
292
|
+
end
|
293
|
+
|
294
|
+
if store_as_binary
|
295
|
+
value.string # expect StringIO here
|
296
|
+
else
|
297
|
+
Base64.strict_decode64(value)
|
298
|
+
end
|
288
299
|
end
|
289
300
|
end
|
290
301
|
|
data/lib/dynamoid/validations.rb
CHANGED
data/lib/dynamoid/version.rb
CHANGED
data/lib/dynamoid.rb
CHANGED
@@ -35,6 +35,7 @@ require 'dynamoid/loadable'
|
|
35
35
|
require 'dynamoid/components'
|
36
36
|
require 'dynamoid/document'
|
37
37
|
require 'dynamoid/adapter'
|
38
|
+
require 'dynamoid/transaction_write'
|
38
39
|
|
39
40
|
require 'dynamoid/tasks/database'
|
40
41
|
|
@@ -62,4 +63,10 @@ module Dynamoid
|
|
62
63
|
def adapter
|
63
64
|
@adapter ||= Adapter.new
|
64
65
|
end
|
66
|
+
|
67
|
+
# @private
|
68
|
+
def deprecator
|
69
|
+
# all the deprecated behavior will be removed in the next major version
|
70
|
+
@deprecator ||= ActiveSupport::Deprecation.new('4.0', 'Dynamoid')
|
71
|
+
end
|
65
72
|
end
|