dynamoid 3.13.0 → 3.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +41 -4
- data/README.md +60 -1584
- data/lib/dynamoid/associations/association.rb +10 -2
- data/lib/dynamoid/associations/belongs_to.rb +3 -1
- data/lib/dynamoid/associations/has_and_belongs_to_many.rb +0 -1
- data/lib/dynamoid/associations/has_many.rb +0 -1
- data/lib/dynamoid/associations/has_one.rb +1 -2
- data/lib/dynamoid/associations/many_association.rb +1 -0
- data/lib/dynamoid/associations.rb +64 -48
- data/lib/dynamoid/components.rb +1 -0
- data/lib/dynamoid/criteria/chain.rb +43 -30
- data/lib/dynamoid/dirty.rb +13 -3
- data/lib/dynamoid/document.rb +29 -21
- data/lib/dynamoid/fields.rb +55 -41
- data/lib/dynamoid/finders.rb +1 -1
- data/lib/dynamoid/indexes.rb +29 -21
- data/lib/dynamoid/persistence/inc.rb +15 -7
- data/lib/dynamoid/persistence/save.rb +21 -14
- data/lib/dynamoid/persistence.rb +88 -54
- data/lib/dynamoid/transactions/mutation/base.rb +76 -0
- data/lib/dynamoid/transactions/mutation/builders/delete_request_builder.rb +44 -0
- data/lib/dynamoid/transactions/mutation/builders/update_request_builder.rb +139 -0
- data/lib/dynamoid/transactions/mutation/create.rb +52 -0
- data/lib/dynamoid/transactions/mutation/delete_with_instance.rb +80 -0
- data/lib/dynamoid/transactions/mutation/delete_with_primary_key.rb +62 -0
- data/lib/dynamoid/transactions/mutation/destroy.rb +96 -0
- data/lib/dynamoid/transactions/mutation/import.rb +84 -0
- data/lib/dynamoid/transactions/mutation/inc.rb +101 -0
- data/lib/dynamoid/transactions/mutation/increment.rb +63 -0
- data/lib/dynamoid/transactions/mutation/save.rb +194 -0
- data/lib/dynamoid/transactions/mutation/touch.rb +99 -0
- data/lib/dynamoid/transactions/mutation/update_attributes.rb +49 -0
- data/lib/dynamoid/transactions/mutation/update_fields.rb +185 -0
- data/lib/dynamoid/transactions/mutation/upsert.rb +94 -0
- data/lib/dynamoid/transactions/mutation.rb +935 -0
- data/lib/dynamoid/transactions/retrieval/find.rb +140 -0
- data/lib/dynamoid/transactions/retrieval.rb +150 -0
- data/lib/dynamoid/transactions.rb +63 -0
- data/lib/dynamoid/version.rb +1 -1
- data/lib/dynamoid.rb +1 -2
- metadata +23 -17
- data/lib/dynamoid/transaction_read/find.rb +0 -137
- data/lib/dynamoid/transaction_read.rb +0 -146
- data/lib/dynamoid/transaction_write/base.rb +0 -59
- data/lib/dynamoid/transaction_write/create.rb +0 -49
- data/lib/dynamoid/transaction_write/delete_with_instance.rb +0 -65
- data/lib/dynamoid/transaction_write/delete_with_primary_key.rb +0 -64
- data/lib/dynamoid/transaction_write/destroy.rb +0 -84
- data/lib/dynamoid/transaction_write/item_updater.rb +0 -60
- data/lib/dynamoid/transaction_write/save.rb +0 -177
- data/lib/dynamoid/transaction_write/update_attributes.rb +0 -46
- data/lib/dynamoid/transaction_write/update_fields.rb +0 -247
- data/lib/dynamoid/transaction_write/upsert.rb +0 -113
- data/lib/dynamoid/transaction_write.rb +0 -673
|
@@ -0,0 +1,935 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'mutation/create'
|
|
4
|
+
require_relative 'mutation/delete_with_primary_key'
|
|
5
|
+
require_relative 'mutation/delete_with_instance'
|
|
6
|
+
require_relative 'mutation/destroy'
|
|
7
|
+
require_relative 'mutation/save'
|
|
8
|
+
require_relative 'mutation/update_fields'
|
|
9
|
+
require_relative 'mutation/update_attributes'
|
|
10
|
+
require_relative 'mutation/upsert'
|
|
11
|
+
require_relative 'mutation/inc'
|
|
12
|
+
require_relative 'mutation/touch'
|
|
13
|
+
require_relative 'mutation/increment'
|
|
14
|
+
require_relative 'mutation/import'
|
|
15
|
+
|
|
16
|
+
module Dynamoid
|
|
17
|
+
module Transactions
|
|
18
|
+
# The +Mutation+ class provides a way to perform multiple modifying operations
|
|
19
|
+
# atomically in a transaction—either all operations succeed, or all fail.
|
|
20
|
+
#
|
|
21
|
+
# The persistence methods are designed to mirror their non-transactional
|
|
22
|
+
# counterparts like +.create+, +#save+, and +#delete+:
|
|
23
|
+
#
|
|
24
|
+
# ```
|
|
25
|
+
# user = User.new(name: 'John')
|
|
26
|
+
# payment = Payment.find(1)
|
|
27
|
+
#
|
|
28
|
+
# Dynamoid::Transactions::Mutation.execute do |t|
|
|
29
|
+
# t.save! user
|
|
30
|
+
# t.create! Account, name: 'A'
|
|
31
|
+
# t.delete payment
|
|
32
|
+
# end
|
|
33
|
+
# ```
|
|
34
|
+
#
|
|
35
|
+
# The primary difference is that these methods are called on a transaction
|
|
36
|
+
# instance, and the model (or class) must be passed as an argument.
|
|
37
|
+
#
|
|
38
|
+
# For example, +user.save!+ becomes +t.save!(user)+, +Account.create!(name: 'A')+
|
|
39
|
+
# becomes +t.create!(Account, name: 'A')+, and +payment.delete+ becomes
|
|
40
|
+
# +t.delete(payment)+.
|
|
41
|
+
#
|
|
42
|
+
# Transactions can also be used without a block by manually instantiating
|
|
43
|
+
# and committing:
|
|
44
|
+
#
|
|
45
|
+
# t = Dynamoid::Transactions::Mutation.new
|
|
46
|
+
# t.save! user
|
|
47
|
+
# t.create! Account, name: 'A'
|
|
48
|
+
# t.delete payment
|
|
49
|
+
# t.commit
|
|
50
|
+
#
|
|
51
|
+
# Some persistence methods (like +.update+ and +.update!+) are intentionally
|
|
52
|
+
# unavailable because they perform multiple underlying operations and cannot
|
|
53
|
+
# be implemented atomically.
|
|
54
|
+
#
|
|
55
|
+
# ### DynamoDB Transactions
|
|
56
|
+
#
|
|
57
|
+
# Unlike many databases, DynamoDB transactions are executed in batch.
|
|
58
|
+
# In Dynamoid, no changes are persisted when a method like +#save+ is called.
|
|
59
|
+
# All changes are queued and sent to DynamoDB at the end of the transaction.
|
|
60
|
+
#
|
|
61
|
+
# This uses the DynamoDB +TransactWriteItems+ operation (see
|
|
62
|
+
# [documentation](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_TransactWriteItems.html)).
|
|
63
|
+
#
|
|
64
|
+
# ### Callbacks
|
|
65
|
+
#
|
|
66
|
+
# Transactional methods support +before_+, +after_+, and +around_+ callbacks
|
|
67
|
+
# to the same extent as their non-transactional counterparts.
|
|
68
|
+
#
|
|
69
|
+
# **Important difference:** Callbacks (even +after_+ ones) run immediately
|
|
70
|
+
# when the method is called, *before* changes are actually persisted to
|
|
71
|
+
# DynamoDB. Consequently, code in an +after_+ callback will not yet see the
|
|
72
|
+
# updated data in DynamoDB.
|
|
73
|
+
#
|
|
74
|
+
# If a callback aborts the operation or a model is invalid, that specific
|
|
75
|
+
# action is skipped, but the transaction itself may still commit successfully.
|
|
76
|
+
#
|
|
77
|
+
# ### Transaction Rollback
|
|
78
|
+
#
|
|
79
|
+
# A transaction is automatically rolled back on the DynamoDB side if:
|
|
80
|
+
# - Another operation is currently updating the same item.
|
|
81
|
+
# - Provisioned capacity is insufficient.
|
|
82
|
+
# - Item or transaction size limits are exceeded (e.g., item > 400 KB or total > 4 MB).
|
|
83
|
+
# - There is a user error (e.g., invalid data format).
|
|
84
|
+
#
|
|
85
|
+
# Since no changes are persisted until the +#commit+ call, a transaction can
|
|
86
|
+
# be aborted by simply raising an exception within the block.
|
|
87
|
+
#
|
|
88
|
+
# Raising +Dynamoid::Errors::Rollback+ will interrupt the transaction without
|
|
89
|
+
# propagating the exception further:
|
|
90
|
+
#
|
|
91
|
+
# ```
|
|
92
|
+
# Dynamoid::Transactions::Mutation.execute do |t|
|
|
93
|
+
# t.save! user
|
|
94
|
+
# t.create! Account, name: 'A'
|
|
95
|
+
#
|
|
96
|
+
# raise Dynamoid::Errors::Rollback if user.is_admin?
|
|
97
|
+
# end
|
|
98
|
+
# ```
|
|
99
|
+
#
|
|
100
|
+
# When a transaction is successfully committed or rolled back, the corresponding
|
|
101
|
+
# +#after_commit+ or +#after_rollback+ callbacks are run for each involved model.
|
|
102
|
+
class Mutation
|
|
103
|
+
def self.execute
|
|
104
|
+
transaction = new
|
|
105
|
+
|
|
106
|
+
begin
|
|
107
|
+
yield transaction
|
|
108
|
+
rescue StandardError => e
|
|
109
|
+
transaction.rollback
|
|
110
|
+
|
|
111
|
+
unless e.is_a?(Dynamoid::Errors::Rollback)
|
|
112
|
+
raise e
|
|
113
|
+
end
|
|
114
|
+
else
|
|
115
|
+
transaction.commit
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def initialize
|
|
120
|
+
@actions = []
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Persist all the changes.
|
|
124
|
+
#
|
|
125
|
+
# transaction = Dynamoid::Transactions::Mutation.new
|
|
126
|
+
# # ...
|
|
127
|
+
# transaction.commit
|
|
128
|
+
def commit
|
|
129
|
+
actions_to_commit = @actions.reject(&:aborted?).reject(&:skipped?)
|
|
130
|
+
return if actions_to_commit.empty?
|
|
131
|
+
|
|
132
|
+
action_requests = actions_to_commit.flat_map(&:action_requests)
|
|
133
|
+
Dynamoid.adapter.transact_write_items(action_requests)
|
|
134
|
+
actions_to_commit.each(&:on_commit)
|
|
135
|
+
|
|
136
|
+
nil
|
|
137
|
+
rescue Aws::Errors::ServiceError
|
|
138
|
+
run_on_rollback_callbacks
|
|
139
|
+
raise
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def rollback
|
|
143
|
+
run_on_rollback_callbacks
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Update the +updated_at+ timestamp and optionally other specified
|
|
147
|
+
# attributes.
|
|
148
|
+
#
|
|
149
|
+
# Runs callbacks.
|
|
150
|
+
#
|
|
151
|
+
# ```
|
|
152
|
+
# Dynamoid::Transactions::Mutation.execute do |t|
|
|
153
|
+
# t.touch(user)
|
|
154
|
+
# end
|
|
155
|
+
# ```
|
|
156
|
+
#
|
|
157
|
+
# If attribute names are passed, they are updated along with updated_at
|
|
158
|
+
# attribute:
|
|
159
|
+
#
|
|
160
|
+
# t.touch(user, :viewed_at)
|
|
161
|
+
# t.touch(user, :viewed_at, :accessed_at)
|
|
162
|
+
#
|
|
163
|
+
# t.touch(user, time: 2.days.ago)
|
|
164
|
+
#
|
|
165
|
+
# Raises +Dynamoid::Errors::Error+ if a model is new or destroyed.
|
|
166
|
+
#
|
|
167
|
+
# @param model [Dynamoid::Document] a model
|
|
168
|
+
# @param names [Array<Symbol>] (optional) attribute names to update
|
|
169
|
+
# @param time [Time] datetime value that can be used instead of the current time (optional)
|
|
170
|
+
# @return [Dynamoid::Document] self
|
|
171
|
+
def touch(model, *names, time: nil)
|
|
172
|
+
action = Touch.new(model, *names, time: time)
|
|
173
|
+
register_action action
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Create new model or persist changes in already existing one.
|
|
177
|
+
#
|
|
178
|
+
# Run the validation and callbacks. Returns +true+ if saving is successful
|
|
179
|
+
# and +false+ otherwise.
|
|
180
|
+
#
|
|
181
|
+
# ```
|
|
182
|
+
# user = User.new
|
|
183
|
+
#
|
|
184
|
+
# Dynamoid::Transactions::Mutation.execute do |t|
|
|
185
|
+
# t.save!(user)
|
|
186
|
+
# end
|
|
187
|
+
# ```
|
|
188
|
+
#
|
|
189
|
+
# Validation can be skipped with +validate: false+ option:
|
|
190
|
+
#
|
|
191
|
+
# ```
|
|
192
|
+
# user = User.new(age: -1)
|
|
193
|
+
#
|
|
194
|
+
# Dynamoid::Transactions::Mutation.execute do |t|
|
|
195
|
+
# t.save!(user, validate: false)
|
|
196
|
+
# end
|
|
197
|
+
# ```
|
|
198
|
+
#
|
|
199
|
+
# +save!+ by default sets timestamps attributes - +created_at+ and
|
|
200
|
+
# +updated_at+ when creates new model and updates +updated_at+ attribute
|
|
201
|
+
# when updates already existing one.
|
|
202
|
+
#
|
|
203
|
+
# If a model is new and hash key (+id+ by default) is not assigned yet
|
|
204
|
+
# it was assigned implicitly with random UUID value.
|
|
205
|
+
#
|
|
206
|
+
# When a model is not persisted - its id should have unique value.
|
|
207
|
+
# Otherwise a transaction will be rolled back.
|
|
208
|
+
#
|
|
209
|
+
# Raises +Dynamoid::Errors::MissingHashKey+ if a model is already persisted
|
|
210
|
+
# and a partition key has value +nil+ and raises
|
|
211
|
+
# +Dynamoid::Errors::MissingRangeKey+ if a sort key is required but has
|
|
212
|
+
# value +nil+.
|
|
213
|
+
#
|
|
214
|
+
# There are the following differences between transactional and
|
|
215
|
+
# non-transactional +#save!+:
|
|
216
|
+
# - transactional +#save!+ doesn't raise +Dynamoid::Errors::StaleObjectError+
|
|
217
|
+
# when optimistic concurrency control detects a conflict. A generic
|
|
218
|
+
# +Aws::DynamoDB::Errors::TransactionCanceledException+ is raised instead.
|
|
219
|
+
# - transactional +#save!+ doesn't raise +Dynamoid::Errors::RecordNotUnique+
|
|
220
|
+
# at saving new model when primary key is already used. A generic
|
|
221
|
+
# +Aws::DynamoDB::Errors::TransactionCanceledException+ is raised instead.
|
|
222
|
+
# - transactional +save!+ doesn't raise
|
|
223
|
+
# +Dynamoid::Errors::StaleObjectError+ when a model that is being updated
|
|
224
|
+
# was concurrently deleted
|
|
225
|
+
# - a table isn't created lazily if it doesn't exist yet
|
|
226
|
+
#
|
|
227
|
+
# @param model [Dynamoid::Document] a model
|
|
228
|
+
# @param options [Hash] (optional)
|
|
229
|
+
# @option options [true|false] :validate validate a model or not - +true+ by default (optional)
|
|
230
|
+
# @option options [true|false] :touch update tiemstamps fields or not - +true+ by default (optional)
|
|
231
|
+
# @return [true|false] Whether saving successful or not
|
|
232
|
+
def save!(model, **options)
|
|
233
|
+
action = Save.new(model, **options, raise_error: true)
|
|
234
|
+
register_action action
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# Create new model or persist changes in already existing one.
|
|
238
|
+
#
|
|
239
|
+
# Run the validation and callbacks. Raise
|
|
240
|
+
# +Dynamoid::Errors::DocumentNotValid+ unless this object is valid.
|
|
241
|
+
#
|
|
242
|
+
# ```
|
|
243
|
+
# user = User.new
|
|
244
|
+
#
|
|
245
|
+
# Dynamoid::Transactions::Mutation.execute do |t|
|
|
246
|
+
# t.save(user)
|
|
247
|
+
# end
|
|
248
|
+
# ```
|
|
249
|
+
#
|
|
250
|
+
# Validation can be skipped with +validate: false+ option:
|
|
251
|
+
#
|
|
252
|
+
# ```
|
|
253
|
+
# user = User.new(age: -1)
|
|
254
|
+
#
|
|
255
|
+
# Dynamoid::Transactions::Mutation.execute do |t|
|
|
256
|
+
# t.save(user, validate: false)
|
|
257
|
+
# end
|
|
258
|
+
# ```
|
|
259
|
+
#
|
|
260
|
+
# +save+ by default sets timestamps attributes - +created_at+ and
|
|
261
|
+
# +updated_at+ when creates new model and updates +updated_at+ attribute
|
|
262
|
+
# when updates already existing one.
|
|
263
|
+
#
|
|
264
|
+
# If a model is new and hash key (+id+ by default) is not assigned yet
|
|
265
|
+
# it was assigned implicitly with random UUID value.
|
|
266
|
+
#
|
|
267
|
+
# When a model is not persisted - its id should have unique value.
|
|
268
|
+
# Otherwise a transaction will be rolled back.
|
|
269
|
+
#
|
|
270
|
+
# Raises +Dynamoid::Errors::MissingHashKey+ if a model is already persisted
|
|
271
|
+
# and a partition key has value +nil+ and raises
|
|
272
|
+
# +Dynamoid::Errors::MissingRangeKey+ if a sort key is required but has
|
|
273
|
+
# value +nil+.
|
|
274
|
+
#
|
|
275
|
+
# There are the following differences between transactional and
|
|
276
|
+
# non-transactional +#save+:
|
|
277
|
+
# - transactional +#save+ doesn't raise +Dynamoid::Errors::StaleObjectError+
|
|
278
|
+
# when optimistic concurrency control detects a conflict. A generic
|
|
279
|
+
# +Aws::DynamoDB::Errors::TransactionCanceledException+ is raised instead.
|
|
280
|
+
# - transactional +#save+ doesn't raise +Dynamoid::Errors::RecordNotUnique+
|
|
281
|
+
# at saving new model when primary key is already used. A generic
|
|
282
|
+
# +Aws::DynamoDB::Errors::TransactionCanceledException+ is raised instead.
|
|
283
|
+
# - transactional +save+ doesn't raise +Dynamoid::Errors::StaleObjectError+
|
|
284
|
+
# when a model that is being updated was concurrently deleted
|
|
285
|
+
# - a table isn't created lazily if it doesn't exist yet
|
|
286
|
+
#
|
|
287
|
+
# @param model [Dynamoid::Document] a model
|
|
288
|
+
# @param options [Hash] (optional)
|
|
289
|
+
# @option options [true|false] :validate validate a model or not - +true+ by default (optional)
|
|
290
|
+
# @option options [true|false] :touch update tiemstamps fields or not - +true+ by default (optional)
|
|
291
|
+
# @return [true|false] Whether saving successful or not
|
|
292
|
+
def save(model, **options)
|
|
293
|
+
action = Save.new(model, **options, raise_error: false)
|
|
294
|
+
register_action action
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
# Create a model.
|
|
298
|
+
#
|
|
299
|
+
# ```
|
|
300
|
+
# Dynamoid::Transactions::Mutation.execute do |t|
|
|
301
|
+
# t.create!(User, name: 'A')
|
|
302
|
+
# end
|
|
303
|
+
# ```
|
|
304
|
+
#
|
|
305
|
+
# Accepts both Hash and Array of Hashes and can create several models.
|
|
306
|
+
#
|
|
307
|
+
# ```
|
|
308
|
+
# Dynamoid::Transactions::Mutation.execute do |t|
|
|
309
|
+
# t.create!(User, [{name: 'A'}, {name: 'B'}, {name: 'C'}])
|
|
310
|
+
# end
|
|
311
|
+
# ```
|
|
312
|
+
#
|
|
313
|
+
# Instantiates a model and pass it into an optional block to set other
|
|
314
|
+
# attributes.
|
|
315
|
+
#
|
|
316
|
+
# ```
|
|
317
|
+
# Dynamoid::Transactions::Mutation.execute do |t|
|
|
318
|
+
# t.create!(User, name: 'A') do |user|
|
|
319
|
+
# user.initialize_roles
|
|
320
|
+
# end
|
|
321
|
+
# end
|
|
322
|
+
# ```
|
|
323
|
+
#
|
|
324
|
+
# Validates model and runs callbacks.
|
|
325
|
+
#
|
|
326
|
+
# Raises +Dynamoid::Errors::MissingRangeKey+ if a sort key is required but
|
|
327
|
+
# not specified or has value +nil+.
|
|
328
|
+
#
|
|
329
|
+
# There are the following differences between transactional and
|
|
330
|
+
# non-transactional +#create+:
|
|
331
|
+
# - transactional +#create!+ doesn't raise +Dynamoid::Errors::RecordNotUnique+
|
|
332
|
+
# at saving new model when primary key is already used. A generic
|
|
333
|
+
# +Aws::DynamoDB::Errors::TransactionCanceledException+ is raised instead.
|
|
334
|
+
# - a table isn't created lazily if it doesn't exist yet
|
|
335
|
+
#
|
|
336
|
+
# @param model_class [Class] a model class which should be instantiated
|
|
337
|
+
# @param attributes [Hash|Array<Hash>] attributes of a model
|
|
338
|
+
# @param block [Proc] a block to process a model after initialization
|
|
339
|
+
# @return [Dynamoid::Document] a model that was instantiated but not yet persisted
|
|
340
|
+
def create!(model_class, attributes = {}, &block)
|
|
341
|
+
if attributes.is_a? Array
|
|
342
|
+
attributes.map do |attr|
|
|
343
|
+
action = Create.new(model_class, attr, raise_error: true, &block)
|
|
344
|
+
register_action action
|
|
345
|
+
end
|
|
346
|
+
else
|
|
347
|
+
action = Create.new(model_class, attributes, raise_error: true, &block)
|
|
348
|
+
register_action action
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
# Create a model.
|
|
353
|
+
#
|
|
354
|
+
# ```
|
|
355
|
+
# Dynamoid::Transactions::Mutation.execute do |t|
|
|
356
|
+
# t.create(User, name: 'A')
|
|
357
|
+
# end
|
|
358
|
+
# ```
|
|
359
|
+
#
|
|
360
|
+
# Accepts both Hash and Array of Hashes and can create several models.
|
|
361
|
+
#
|
|
362
|
+
# ```
|
|
363
|
+
# Dynamoid::Transactions::Mutation.execute do |t|
|
|
364
|
+
# t.create(User, [{name: 'A'}, {name: 'B'}, {name: 'C'}])
|
|
365
|
+
# end
|
|
366
|
+
# ```
|
|
367
|
+
#
|
|
368
|
+
# Instantiates a model and pass it into an optional block to set other
|
|
369
|
+
# attributes.
|
|
370
|
+
#
|
|
371
|
+
# ```
|
|
372
|
+
# Dynamoid::Transactions::Mutation.execute do |t|
|
|
373
|
+
# t.create(User, name: 'A') do |user|
|
|
374
|
+
# user.initialize_roles
|
|
375
|
+
# end
|
|
376
|
+
# end
|
|
377
|
+
# ```
|
|
378
|
+
#
|
|
379
|
+
# Validates model and runs callbacks.
|
|
380
|
+
#
|
|
381
|
+
# Raises +Dynamoid::Errors::MissingRangeKey+ if a sort key is required but
|
|
382
|
+
# not specified or has value +nil+.
|
|
383
|
+
#
|
|
384
|
+
# There are the following differences between transactional and
|
|
385
|
+
# non-transactional +#create+:
|
|
386
|
+
# - transactional +#create+ doesn't raise +Dynamoid::Errors::RecordNotUnique+
|
|
387
|
+
# at saving new model when primary key is already used. A generic
|
|
388
|
+
# +Aws::DynamoDB::Errors::TransactionCanceledException+ is raised instead.
|
|
389
|
+
# - a table isn't created lazily if it doesn't exist yet
|
|
390
|
+
#
|
|
391
|
+
# @param model_class [Class] a model class which should be instantiated
|
|
392
|
+
# @param attributes [Hash|Array<Hash>] attributes of a model
|
|
393
|
+
# @param block [Proc] a block to process a model after initialization
|
|
394
|
+
# @return [Dynamoid::Document] a model that was instantiated but not yet persisted
|
|
395
|
+
def create(model_class, attributes = {}, &block)
|
|
396
|
+
if attributes.is_a? Array
|
|
397
|
+
attributes.map do |attr|
|
|
398
|
+
action = Create.new(model_class, attr, raise_error: false, &block)
|
|
399
|
+
register_action action
|
|
400
|
+
end
|
|
401
|
+
else
|
|
402
|
+
action = Create.new(model_class, attributes, raise_error: false, &block)
|
|
403
|
+
register_action action
|
|
404
|
+
end
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
# Update an existing document or create a new one.
|
|
408
|
+
#
|
|
409
|
+
# If a document with specified hash and range keys doesn't exist it
|
|
410
|
+
# creates a new document with specified attributes. Doesn't run
|
|
411
|
+
# validations and callbacks.
|
|
412
|
+
#
|
|
413
|
+
# ```
|
|
414
|
+
# Dynamoid::Transactions::Mutation.execute do |t|
|
|
415
|
+
# t.upsert(User, '1', age: 26)
|
|
416
|
+
# end
|
|
417
|
+
# ```
|
|
418
|
+
#
|
|
419
|
+
# If range key is declared for a model it should be passed as well:
|
|
420
|
+
#
|
|
421
|
+
# ```
|
|
422
|
+
# Dynamoid::Transactions::Mutation.execute do |t|
|
|
423
|
+
# t.upsert(User, '1', 'Tylor', age: 26)
|
|
424
|
+
# end
|
|
425
|
+
# ```
|
|
426
|
+
#
|
|
427
|
+
# Raises a +Dynamoid::Errors::UnknownAttribute+ exception if any of the
|
|
428
|
+
# attributes is not declared in the model class.
|
|
429
|
+
#
|
|
430
|
+
# Raises +Dynamoid::Errors::MissingHashKey+ if a partition key has value
|
|
431
|
+
# +nil+ and +Dynamoid::Errors::MissingRangeKey+ if a sort key is required
|
|
432
|
+
# but has value +nil+.
|
|
433
|
+
#
|
|
434
|
+
# There are the following differences between transactional and
|
|
435
|
+
# non-transactional +#upsert+:
|
|
436
|
+
# - transactional +#upsert+ doesn't support conditions (that's +if+ and
|
|
437
|
+
# +unless_exists+ options)
|
|
438
|
+
# - transactional +#upsert+ doesn't return a document that was updated or
|
|
439
|
+
# created
|
|
440
|
+
#
|
|
441
|
+
# @param model_class [Class] a model class
|
|
442
|
+
# @param hash_key [Scalar value] hash key value
|
|
443
|
+
# @param range_key [Scalar value] range key value (optional)
|
|
444
|
+
# @param attributes [Hash]
|
|
445
|
+
# @return [nil]
|
|
446
|
+
def upsert(model_class, hash_key, range_key = nil, attributes) # rubocop:disable Style/OptionalArguments
|
|
447
|
+
action = Upsert.new(model_class, hash_key, range_key, attributes)
|
|
448
|
+
register_action action
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
# Update document.
|
|
452
|
+
#
|
|
453
|
+
# Doesn't run validations and callbacks.
|
|
454
|
+
#
|
|
455
|
+
# ```
|
|
456
|
+
# Dynamoid::Transactions::Mutation.execute do |t|
|
|
457
|
+
# t.update_fields(User, '1', age: 26)
|
|
458
|
+
# end
|
|
459
|
+
# ```
|
|
460
|
+
#
|
|
461
|
+
# If range key is declared for a model it should be passed as well:
|
|
462
|
+
#
|
|
463
|
+
# ```
|
|
464
|
+
# Dynamoid::Transactions::Mutation.execute do |t|
|
|
465
|
+
# t.update_fields(User, '1', 'Tylor', age: 26)
|
|
466
|
+
# end
|
|
467
|
+
# ```
|
|
468
|
+
#
|
|
469
|
+
# Updates can also be performed in a block.
|
|
470
|
+
#
|
|
471
|
+
# ```
|
|
472
|
+
# Dynamoid::Transactions::Mutation.execute do |t|
|
|
473
|
+
# t.update_fields(User, 1) do |u|
|
|
474
|
+
# u.add(article_count: 1)
|
|
475
|
+
# u.delete(favorite_colors: 'green')
|
|
476
|
+
# u.set(age: 27, last_name: 'Tylor')
|
|
477
|
+
# end
|
|
478
|
+
# end
|
|
479
|
+
# ```
|
|
480
|
+
#
|
|
481
|
+
# Operation +add+ just adds a value for numeric attributes and join
|
|
482
|
+
# collections if attribute is a set.
|
|
483
|
+
#
|
|
484
|
+
# ```
|
|
485
|
+
# t.update_fields(User, 1) do |u|
|
|
486
|
+
# u.add(age: 1, followers_count: 5)
|
|
487
|
+
# u.add(hobbies: ['skying', 'climbing'])
|
|
488
|
+
# end
|
|
489
|
+
# ```
|
|
490
|
+
#
|
|
491
|
+
# Operation +delete+ is applied to collection attribute types and
|
|
492
|
+
# substructs one collection from another.
|
|
493
|
+
#
|
|
494
|
+
# ```
|
|
495
|
+
# t.update_fields(User, 1) do |u|
|
|
496
|
+
# u.delete(hobbies: ['skying'])
|
|
497
|
+
# end
|
|
498
|
+
# ```
|
|
499
|
+
#
|
|
500
|
+
# Operation +set+ just changes an attribute value:
|
|
501
|
+
#
|
|
502
|
+
# ```
|
|
503
|
+
# t.update_fields(User, 1) do |u|
|
|
504
|
+
# u.set(age: 21)
|
|
505
|
+
# end
|
|
506
|
+
# ```
|
|
507
|
+
#
|
|
508
|
+
# Operation +remove+ removes one or more attributes from an item.
|
|
509
|
+
#
|
|
510
|
+
# ```
|
|
511
|
+
# t.update_fields(User, 1) do |u|
|
|
512
|
+
# u.remove(:age)
|
|
513
|
+
# end
|
|
514
|
+
# ```
|
|
515
|
+
#
|
|
516
|
+
# All the operations work like +ADD+, +DELETE+, +REMOVE+, and +SET+ actions supported
|
|
517
|
+
# by +UpdateExpression+
|
|
518
|
+
# {parameter}[https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html]
|
|
519
|
+
# of +UpdateItem+ operation.
|
|
520
|
+
#
|
|
521
|
+
# It's atomic operations. So adding or deleting elements in a collection
|
|
522
|
+
# or incrementing or decrementing a numeric field is atomic and does not
|
|
523
|
+
# interfere with other write requests.
|
|
524
|
+
#
|
|
525
|
+
# Raises a +Dynamoid::Errors::UnknownAttribute+ exception if any of the
|
|
526
|
+
# attributes is not declared in the model class.
|
|
527
|
+
#
|
|
528
|
+
# Raises +Dynamoid::Errors::MissingHashKey+ if a partition key has value
|
|
529
|
+
# +nil+ and +Dynamoid::Errors::MissingRangeKey+ if a sort key is required
|
|
530
|
+
# but has value +nil+.
|
|
531
|
+
#
|
|
532
|
+
# There are the following differences between transactional and
|
|
533
|
+
# non-transactional +#update_fields+:
|
|
534
|
+
# - transactional +#update_fields+ doesn't support conditions (that's +if+
|
|
535
|
+
# and +unless_exists+ options)
|
|
536
|
+
# - transactional +#update_fields+ doesn't return a document that was
|
|
537
|
+
# updated or created
|
|
538
|
+
#
|
|
539
|
+
# @param model_class [Class] a model class
|
|
540
|
+
# @param hash_key [Scalar value] hash key value
|
|
541
|
+
# @param range_key [Scalar value] range key value (optional)
|
|
542
|
+
# @param attributes [Hash]
|
|
543
|
+
# @return [nil]
|
|
544
|
+
def update_fields(model_class, hash_key, range_key = nil, attributes = nil, &block)
|
|
545
|
+
# given no attributes, but there may be a block
|
|
546
|
+
if range_key.is_a?(Hash) && !attributes
|
|
547
|
+
attributes = range_key
|
|
548
|
+
range_key = nil
|
|
549
|
+
end
|
|
550
|
+
|
|
551
|
+
action = UpdateFields.new(model_class, hash_key, range_key, attributes, &block)
|
|
552
|
+
register_action action
|
|
553
|
+
end
|
|
554
|
+
|
|
555
|
+
# Increment numeric attributes.
|
|
556
|
+
#
|
|
557
|
+
# Doesn't run validations and callbacks.
|
|
558
|
+
#
|
|
559
|
+
# ```
|
|
560
|
+
# Dynamoid::Transactions::Mutation.execute do |t|
|
|
561
|
+
# t.inc(User, '1', age: 1)
|
|
562
|
+
# end
|
|
563
|
+
# ```
|
|
564
|
+
#
|
|
565
|
+
# If range key is declared for a model it should be passed as well:
|
|
566
|
+
#
|
|
567
|
+
# ```
|
|
568
|
+
# Dynamoid::Transactions::Mutation.execute do |t|
|
|
569
|
+
# t.inc(User, '1', 'Tylor', age: 1)
|
|
570
|
+
# end
|
|
571
|
+
# ```
|
|
572
|
+
#
|
|
573
|
+
# It also supports +touch+ option to update +updated_at+ attribute and
|
|
574
|
+
# optionally other specified attributes.
|
|
575
|
+
#
|
|
576
|
+
# ```
|
|
577
|
+
# Dynamoid::Transactions::Mutation.execute do |t|
|
|
578
|
+
# t.inc(User, '1', age: 1, touch: true)
|
|
579
|
+
# end
|
|
580
|
+
# ```
|
|
581
|
+
#
|
|
582
|
+
# If attribute names are passed, they are updated along with updated_at
|
|
583
|
+
# attribute:
|
|
584
|
+
#
|
|
585
|
+
# t.inc(User, '1', age: 2, touch: :viewed_at)
|
|
586
|
+
# t.inc(User, '1', age: 2, touch: [:viewed_at, :accessed_at])
|
|
587
|
+
#
|
|
588
|
+
# It's an atomic operation. So incrementing or decrementing a numeric
|
|
589
|
+
# field is atomic and does not interfere with other write requests.
|
|
590
|
+
#
|
|
591
|
+
# Raises a +Dynamoid::Errors::UnknownAttribute+ exception if any of the
|
|
592
|
+
# attributes is not declared in the model class.
|
|
593
|
+
#
|
|
594
|
+
# Raises +Dynamoid::Errors::MissingHashKey+ if a partition key has value
|
|
595
|
+
# +nil+ and +Dynamoid::Errors::MissingRangeKey+ if a sort key is required
|
|
596
|
+
# but has value +nil+.
|
|
597
|
+
#
|
|
598
|
+
# @param model_class [Class] a model class
|
|
599
|
+
# @param hash_key [Scalar value] hash key value
|
|
600
|
+
# @param range_key [Scalar value] range key value (optional)
|
|
601
|
+
# @param counters [Hash]
|
|
602
|
+
# @return [nil]
|
|
603
|
+
def inc(model_class, hash_key, range_key = nil, counters = nil)
|
|
604
|
+
if range_key.is_a?(Hash) && !counters
|
|
605
|
+
counters = range_key
|
|
606
|
+
range_key = nil
|
|
607
|
+
end
|
|
608
|
+
|
|
609
|
+
action = Inc.new(model_class, hash_key, range_key, counters)
|
|
610
|
+
register_action action
|
|
611
|
+
end
|
|
612
|
+
|
|
613
|
+
# Change numeric attribute value and save a model.
|
|
614
|
+
#
|
|
615
|
+
# Initializes attribute to zero if +nil+ and adds the specified value (by
|
|
616
|
+
# default is 1). Only makes sense for number-based attributes.
|
|
617
|
+
#
|
|
618
|
+
# ```
|
|
619
|
+
# Dynamoid::Transactions::Mutation.execute do |t|
|
|
620
|
+
# t.increment!(user, :followers_count)
|
|
621
|
+
# t.increment!(user, :followers_count, 2)
|
|
622
|
+
# end
|
|
623
|
+
# ```
|
|
624
|
+
#
|
|
625
|
+
# Only `attribute` is saved. The model itself is not saved. So any other
|
|
626
|
+
# modified attributes will still be dirty. Validations and callbacks are
|
|
627
|
+
# skipped.
|
|
628
|
+
#
|
|
629
|
+
# When `:touch` option is passed the timestamp columns are updating. If
|
|
630
|
+
# attribute names are passed, they are updated along with updated_at
|
|
631
|
+
# attribute:
|
|
632
|
+
#
|
|
633
|
+
# t.increment!(user, :followers_count, touch: true)
|
|
634
|
+
# t.increment!(user, :followers_count, touch: :viewed_at)
|
|
635
|
+
# t.increment!(user, :followers_count, touch: [:viewed_at, :accessed_at])
|
|
636
|
+
#
|
|
637
|
+
# @param model [Dynamoid::Document] a model
|
|
638
|
+
# @param attribute [Symbol] attribute name
|
|
639
|
+
# @param by [Numeric] value to add (optional)
|
|
640
|
+
# @param touch [true | Symbol | Array<Symbol>] to update update_at attribute and optionally the specified ones
|
|
641
|
+
# @return [Dynamoid::Document] self
|
|
642
|
+
def increment!(model, attribute, by = 1, touch: nil)
|
|
643
|
+
action = Increment.new(model, attribute, by, touch: touch)
|
|
644
|
+
register_action action
|
|
645
|
+
end
|
|
646
|
+
|
|
647
|
+
# Change numeric attribute value and save a model.
|
|
648
|
+
#
|
|
649
|
+
# Initializes attribute to zero if +nil+ and subtracts the specified value
|
|
650
|
+
# (by default is 1). Only makes sense for number-based attributes.
|
|
651
|
+
#
|
|
652
|
+
# Runs callbacks.
|
|
653
|
+
#
|
|
654
|
+
# ```
|
|
655
|
+
# Dynamoid::Transactions::Mutation.execute do |t|
|
|
656
|
+
# t.decrement!(user, :followers_count)
|
|
657
|
+
# t.decrement!(user, :followers_count, 2)
|
|
658
|
+
# end
|
|
659
|
+
# ```
|
|
660
|
+
#
|
|
661
|
+
# Only `attribute` is saved. The model itself is not saved. So any other
|
|
662
|
+
# modified attributes will still be dirty. Validations are skipped.
|
|
663
|
+
#
|
|
664
|
+
# When `:touch` option is passed the timestamp columns are updating. If
|
|
665
|
+
# attribute names are passed, they are updated along with updated_at
|
|
666
|
+
# attribute:
|
|
667
|
+
#
|
|
668
|
+
# t.decrement!(user, :followers_count, touch: true)
|
|
669
|
+
# t.decrement!(user, :followers_count, touch: :viewed_at)
|
|
670
|
+
# t.decrement!(user, :followers_count, touch: [:viewed_at, :accessed_at])
|
|
671
|
+
#
|
|
672
|
+
# @param model [Dynamoid::Document] a model
|
|
673
|
+
# @param attribute [Symbol] attribute name
|
|
674
|
+
# @param by [Numeric] value to subtract (optional)
|
|
675
|
+
# @param touch [true | Symbol | Array<Symbol>] to update update_at attribute and optionally the specified ones
|
|
676
|
+
# @return [Dynamoid::Document] self
|
|
677
|
+
def decrement!(model, attribute, by = 1, touch: nil)
|
|
678
|
+
increment!(model, attribute, -by, touch: touch)
|
|
679
|
+
end
|
|
680
|
+
|
|
681
|
+
# Update multiple attributes at once.
|
|
682
|
+
#
|
|
683
|
+
# ```
|
|
684
|
+
# Dynamoid::Transactions::Mutation.execute do |t|
|
|
685
|
+
# t.update_attributes(user, age: 27, last_name: 'Tylor')
|
|
686
|
+
# end
|
|
687
|
+
# ```
|
|
688
|
+
#
|
|
689
|
+
# Returns +true+ if saving is successful and +false+
|
|
690
|
+
# otherwise.
|
|
691
|
+
#
|
|
692
|
+
# Raises +Dynamoid::Errors::MissingHashKey+ if a partition key has value
|
|
693
|
+
# +nil+ and raises +Dynamoid::Errors::MissingRangeKey+ if a sort key is
|
|
694
|
+
# required but has value +nil+.
|
|
695
|
+
#
|
|
696
|
+
# There are the following differences between transactional and
|
|
697
|
+
# non-transactional +#update_attributes+:
|
|
698
|
+
# - transactional +#update_attributes+ doesn't raise
|
|
699
|
+
# +Dynamoid::Errors::StaleObjectError+ when optimistic concurrency
|
|
700
|
+
# control detects a conflict. A generic
|
|
701
|
+
# +Aws::DynamoDB::Errors::TransactionCanceledException+ is raised
|
|
702
|
+
# instead.
|
|
703
|
+
# - transactional +update_attributes+ doesn't raise
|
|
704
|
+
# +Dynamoid::Errors::StaleObjectError+ when a model that is being updated
|
|
705
|
+
# was concurrently deleted
|
|
706
|
+
# - a table isn't created lazily if it doesn't exist yet
|
|
707
|
+
#
|
|
708
|
+
# @param model [Dynamoid::Document] a model
|
|
709
|
+
# @param attributes [Hash] a hash of attributes to update
|
|
710
|
+
# @return [true|false] Whether updating successful or not
|
|
711
|
+
def update_attributes(model, attributes)
|
|
712
|
+
action = UpdateAttributes.new(model, attributes, raise_error: false)
|
|
713
|
+
register_action action
|
|
714
|
+
end
|
|
715
|
+
|
|
716
|
+
# Update multiple attributes at once.
|
|
717
|
+
#
|
|
718
|
+
# Returns +true+ if saving is successful and +false+
|
|
719
|
+
# otherwise.
|
|
720
|
+
#
|
|
721
|
+
# ```
|
|
722
|
+
# Dynamoid::Transactions::Mutation.execute do |t|
|
|
723
|
+
# t.update_attributes(user, age: 27, last_name: 'Tylor')
|
|
724
|
+
# end
|
|
725
|
+
# ```
|
|
726
|
+
#
|
|
727
|
+
# Raises a +Dynamoid::Errors::DocumentNotValid+ exception if some vaidation
|
|
728
|
+
# fails.
|
|
729
|
+
#
|
|
730
|
+
# Raises +Dynamoid::Errors::MissingHashKey+ if a partition key has value
|
|
731
|
+
# +nil+ and raises +Dynamoid::Errors::MissingRangeKey+ if a sort key is
|
|
732
|
+
# required but has value +nil+.
|
|
733
|
+
#
|
|
734
|
+
# There are the following differences between transactional and
|
|
735
|
+
# non-transactional +#update_attributes!+:
|
|
736
|
+
# - transactional +#update_attributes!+ doesn't raise
|
|
737
|
+
# +Dynamoid::Errors::StaleObjectError+ when optimistic concurrency
|
|
738
|
+
# control detects a conflict. A generic
|
|
739
|
+
# +Aws::DynamoDB::Errors::TransactionCanceledException+ is raised
|
|
740
|
+
# instead.
|
|
741
|
+
# - transactional +update_attributes!+ doesn't raise
|
|
742
|
+
# +Dynamoid::Errors::StaleObjectError+ when a model that is being updated
|
|
743
|
+
# was concurrently deleted
|
|
744
|
+
# - a table isn't created lazily if it doesn't exist yet
|
|
745
|
+
#
|
|
746
|
+
# @param model [Dynamoid::Document] a model
|
|
747
|
+
# @param attributes [Hash] a hash of attributes to update
|
|
748
|
+
def update_attributes!(model, attributes)
|
|
749
|
+
action = UpdateAttributes.new(model, attributes, raise_error: true)
|
|
750
|
+
register_action action
|
|
751
|
+
end
|
|
752
|
+
|
|
753
|
+
# Update a single attribute, saving the object afterwards.
|
|
754
|
+
#
|
|
755
|
+
# ```
|
|
756
|
+
# Dynamoid::Transactions::Mutation.execute do |t|
|
|
757
|
+
# t.update_attribute(user, :last_name, 'Tylor')
|
|
758
|
+
# end
|
|
759
|
+
# ```
|
|
760
|
+
#
|
|
761
|
+
# Validation is skipped.
|
|
762
|
+
#
|
|
763
|
+
# Raises a +Dynamoid::Errors::UnknownAttribute+ exception if any of the
|
|
764
|
+
# attributes is not on the model
|
|
765
|
+
#
|
|
766
|
+
# @param model [Dynamoid::Document] a model
|
|
767
|
+
# @param attribute [Symbol] attribute name to update
|
|
768
|
+
# @param value [Object] the value to assign it
|
|
769
|
+
# @return [Dynamoid::Document] self
|
|
770
|
+
def update_attribute(model, attribute, value)
|
|
771
|
+
model.write_attribute(attribute, value)
|
|
772
|
+
save(model, validate: false)
|
|
773
|
+
end
|
|
774
|
+
|
|
775
|
+
# Update a single attribute, saving the object afterwards.
|
|
776
|
+
#
|
|
777
|
+
# ```
|
|
778
|
+
# Dynamoid::Transactions::Mutation.execute do |t|
|
|
779
|
+
# t.update_attribute!(user, :last_name, 'Tylor')
|
|
780
|
+
# end
|
|
781
|
+
# ```
|
|
782
|
+
#
|
|
783
|
+
# Validation is skipped.
|
|
784
|
+
#
|
|
785
|
+
# If any of the `before_*` callbacks throws `:abort` the action is
|
|
786
|
+
# cancelled and `update_attribute!` raises
|
|
787
|
+
# Dynamoid::Errors::RecordNotSaved.
|
|
788
|
+
#
|
|
789
|
+
# Raises a +Dynamoid::Errors::UnknownAttribute+ exception if any of the
|
|
790
|
+
# attributes is not on the model
|
|
791
|
+
#
|
|
792
|
+
# @param model [Dynamoid::Document] a model
|
|
793
|
+
# @param attribute [Symbol] attribute name to update
|
|
794
|
+
# @param value [Object] the value to assign it
|
|
795
|
+
# @return [Dynamoid::Document] self
|
|
796
|
+
def update_attribute!(model, attribute, value)
|
|
797
|
+
model.write_attribute(attribute, value)
|
|
798
|
+
save!(model, validate: false)
|
|
799
|
+
model
|
|
800
|
+
end
|
|
801
|
+
|
|
802
|
+
# Delete a model.
|
|
803
|
+
#
|
|
804
|
+
# Can be called either with a model:
|
|
805
|
+
#
|
|
806
|
+
# ```
|
|
807
|
+
# Dynamoid::Transactions::Mutation.execute do |t|
|
|
808
|
+
# t.delete(user)
|
|
809
|
+
# end
|
|
810
|
+
# ```
|
|
811
|
+
#
|
|
812
|
+
# or with a primary key:
|
|
813
|
+
#
|
|
814
|
+
# ```
|
|
815
|
+
# Dynamoid::Transactions::Mutation.execute do |t|
|
|
816
|
+
# t.delete(User, user_id)
|
|
817
|
+
# end
|
|
818
|
+
# ```
|
|
819
|
+
#
|
|
820
|
+
# Raises +Dynamoid::Errors::MissingHashKey+ if a partition key has value
|
|
821
|
+
# +nil+ and raises +Dynamoid::Errors::MissingRangeKey+ if a sort key is
|
|
822
|
+
# required but has value +nil+.
|
|
823
|
+
#
|
|
824
|
+
# There are the following differences between transactional and
|
|
825
|
+
# non-transactional +#delete+: TBD
|
|
826
|
+
# - transactional +#delete+ doesn't raise
|
|
827
|
+
# +Dynamoid::Errors::StaleObjectError+ when optimistic concurrency
|
|
828
|
+
# control detects a conflict. A generic
|
|
829
|
+
# +Aws::DynamoDB::Errors::TransactionCanceledException+ is raised
|
|
830
|
+
# instead.
|
|
831
|
+
# - transactional +#delete+ doesn't disassociate a model from associated ones
|
|
832
|
+
# if there is any
|
|
833
|
+
#
|
|
834
|
+
# @param model_or_model_class [Class|Dynamoid::Document] either model or model class
|
|
835
|
+
# @param hash_key [Scalar value] hash key value
|
|
836
|
+
# @param range_key [Scalar value] range key value (optional)
|
|
837
|
+
# @return [Dynamoid::Document] self
|
|
838
|
+
def delete(model_or_model_class, hash_key = nil, range_key = nil)
|
|
839
|
+
action = if model_or_model_class.is_a? Class
|
|
840
|
+
DeleteWithPrimaryKey.new(model_or_model_class, hash_key, range_key)
|
|
841
|
+
else
|
|
842
|
+
DeleteWithInstance.new(model_or_model_class)
|
|
843
|
+
end
|
|
844
|
+
register_action action
|
|
845
|
+
end
|
|
846
|
+
|
|
847
|
+
# Delete a model.
|
|
848
|
+
#
|
|
849
|
+
# Runs callbacks.
|
|
850
|
+
#
|
|
851
|
+
# Raises +Dynamoid::Errors::RecordNotDestroyed+ exception if model deleting
|
|
852
|
+
# failed (e.g. aborted by a callback).
|
|
853
|
+
#
|
|
854
|
+
# Raises +Dynamoid::Errors::MissingHashKey+ if a partition key has value
|
|
855
|
+
# +nil+ and raises +Dynamoid::Errors::MissingRangeKey+ if a sort key is
|
|
856
|
+
# required but has value +nil+.
|
|
857
|
+
#
|
|
858
|
+
# There are the following differences between transactional and
|
|
859
|
+
# non-transactional +#destroy!+:
|
|
860
|
+
# - transactional +#destroy!+ doesn't raise
|
|
861
|
+
# +Dynamoid::Errors::StaleObjectError+ when optimistic concurrency
|
|
862
|
+
# control detects a conflict. A generic
|
|
863
|
+
# +Aws::DynamoDB::Errors::TransactionCanceledException+ is raised
|
|
864
|
+
# instead.
|
|
865
|
+
# - transactional +#destroy!+ doesn't disassociate a model from associated ones
|
|
866
|
+
# if there are association declared in the model class
|
|
867
|
+
#
|
|
868
|
+
# @param model [Dynamoid::Document] a model
|
|
869
|
+
# @return [Dynamoid::Document|false] returns self if destoying is succefull, +false+ otherwise
|
|
870
|
+
def destroy!(model)
|
|
871
|
+
action = Destroy.new(model, raise_error: true)
|
|
872
|
+
register_action action
|
|
873
|
+
end
|
|
874
|
+
|
|
875
|
+
# Delete a model.
|
|
876
|
+
#
|
|
877
|
+
# Runs callbacks.
|
|
878
|
+
#
|
|
879
|
+
# Raises +Dynamoid::Errors::MissingHashKey+ if a partition key has value
|
|
880
|
+
# +nil+ and raises +Dynamoid::Errors::MissingRangeKey+ if a sort key is
|
|
881
|
+
# required but has value +nil+.
|
|
882
|
+
#
|
|
883
|
+
# There are the following differences between transactional and
|
|
884
|
+
# non-transactional +#destroy+:
|
|
885
|
+
# - transactional +#destroy+ doesn't raise
|
|
886
|
+
# +Dynamoid::Errors::StaleObjectError+ when optimistic concurrency
|
|
887
|
+
# control detects a conflict. A generic
|
|
888
|
+
# +Aws::DynamoDB::Errors::TransactionCanceledException+ is raised
|
|
889
|
+
# instead.
|
|
890
|
+
# - transactional +#destroy+ doesn't disassociate a model from associated ones
|
|
891
|
+
# if there are association declared in the model class
|
|
892
|
+
#
|
|
893
|
+
# @param model [Dynamoid::Document] a model
|
|
894
|
+
# @return [Dynamoid::Document] self
|
|
895
|
+
def destroy(model)
|
|
896
|
+
action = Destroy.new(model, raise_error: false)
|
|
897
|
+
register_action action
|
|
898
|
+
end
|
|
899
|
+
|
|
900
|
+
# Create multiple models from an array of attribute hashes.
|
|
901
|
+
#
|
|
902
|
+
# Validations and callbacks are skipped.
|
|
903
|
+
#
|
|
904
|
+
# ```
|
|
905
|
+
# Dynamoid::Transactions::Mutation.execute do |t|
|
|
906
|
+
# t.import(User, [{ name: 'A' }, { name: 'B' }])
|
|
907
|
+
# end
|
|
908
|
+
# ```
|
|
909
|
+
#
|
|
910
|
+
# Since DynamoDB limits the total number of actions per transaction,
|
|
911
|
+
# each model created via +#import+ consumes one action from this limit.
|
|
912
|
+
#
|
|
913
|
+
# @param model_class [Class] a model class
|
|
914
|
+
# @param array_of_attributes [Array<Hash>] attributes of models
|
|
915
|
+
# @return [Array<Dynamoid::Document>] created models
|
|
916
|
+
def import(model_class, array_of_attributes)
|
|
917
|
+
action = Import.new(model_class, array_of_attributes)
|
|
918
|
+
register_action action
|
|
919
|
+
end
|
|
920
|
+
|
|
921
|
+
private
|
|
922
|
+
|
|
923
|
+
def register_action(action)
|
|
924
|
+
@actions << action
|
|
925
|
+
action.on_registration
|
|
926
|
+
action.observable_by_user_result
|
|
927
|
+
end
|
|
928
|
+
|
|
929
|
+
def run_on_rollback_callbacks
|
|
930
|
+
actions_to_commit = @actions.reject(&:aborted?).reject(&:skipped?)
|
|
931
|
+
actions_to_commit.each(&:on_rollback)
|
|
932
|
+
end
|
|
933
|
+
end
|
|
934
|
+
end
|
|
935
|
+
end
|