dynamoid 3.8.0 → 3.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +89 -2
- data/README.md +375 -64
- data/SECURITY.md +17 -0
- data/dynamoid.gemspec +65 -0
- data/lib/dynamoid/adapter.rb +21 -14
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/create_table.rb +2 -2
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/execute_statement.rb +62 -0
- 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 +29 -2
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/middleware/limit.rb +3 -0
- 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 +132 -74
- data/lib/dynamoid/associations/belongs_to.rb +6 -6
- data/lib/dynamoid/associations.rb +1 -1
- data/lib/dynamoid/components.rb +3 -3
- data/lib/dynamoid/config/options.rb +12 -12
- data/lib/dynamoid/config.rb +4 -0
- data/lib/dynamoid/criteria/chain.rb +165 -149
- 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 +145 -59
- data/lib/dynamoid/document.rb +39 -3
- data/lib/dynamoid/dumping.rb +41 -19
- data/lib/dynamoid/errors.rb +32 -3
- data/lib/dynamoid/fields/declare.rb +6 -6
- data/lib/dynamoid/fields.rb +21 -29
- data/lib/dynamoid/finders.rb +68 -51
- data/lib/dynamoid/indexes.rb +7 -10
- data/lib/dynamoid/loadable.rb +3 -2
- data/lib/dynamoid/log/formatter.rb +19 -4
- data/lib/dynamoid/persistence/import.rb +4 -1
- data/lib/dynamoid/persistence/inc.rb +82 -0
- 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 +75 -17
- data/lib/dynamoid/persistence/update_fields.rb +24 -9
- data/lib/dynamoid/persistence/update_validations.rb +3 -3
- data/lib/dynamoid/persistence/upsert.rb +22 -8
- data/lib/dynamoid/persistence.rb +308 -72
- data/lib/dynamoid/transaction_read/find.rb +137 -0
- data/lib/dynamoid/transaction_read.rb +146 -0
- data/lib/dynamoid/transaction_write/base.rb +47 -0
- data/lib/dynamoid/transaction_write/create.rb +49 -0
- data/lib/dynamoid/transaction_write/delete_with_instance.rb +65 -0
- data/lib/dynamoid/transaction_write/delete_with_primary_key.rb +64 -0
- data/lib/dynamoid/transaction_write/destroy.rb +84 -0
- data/lib/dynamoid/transaction_write/item_updater.rb +55 -0
- data/lib/dynamoid/transaction_write/save.rb +169 -0
- data/lib/dynamoid/transaction_write/update_attributes.rb +46 -0
- data/lib/dynamoid/transaction_write/update_fields.rb +239 -0
- data/lib/dynamoid/transaction_write/upsert.rb +106 -0
- data/lib/dynamoid/transaction_write.rb +673 -0
- data/lib/dynamoid/type_casting.rb +18 -15
- data/lib/dynamoid/undumping.rb +14 -3
- data/lib/dynamoid/validations.rb +8 -5
- data/lib/dynamoid/version.rb +1 -1
- data/lib/dynamoid.rb +8 -0
- metadata +43 -49
- data/lib/dynamoid/criteria/ignored_conditions_detector.rb +0 -41
- data/lib/dynamoid/criteria/overwritten_conditions_detector.rb +0 -40
data/lib/dynamoid/persistence.rb
CHANGED
|
@@ -8,7 +8,9 @@ require 'dynamoid/persistence/import'
|
|
|
8
8
|
require 'dynamoid/persistence/update_fields'
|
|
9
9
|
require 'dynamoid/persistence/upsert'
|
|
10
10
|
require 'dynamoid/persistence/save'
|
|
11
|
+
require 'dynamoid/persistence/inc'
|
|
11
12
|
require 'dynamoid/persistence/update_validations'
|
|
13
|
+
require 'dynamoid/persistence/item_updater_with_dumping'
|
|
12
14
|
|
|
13
15
|
# encoding: utf-8
|
|
14
16
|
module Dynamoid
|
|
@@ -17,8 +19,9 @@ module Dynamoid
|
|
|
17
19
|
module Persistence
|
|
18
20
|
extend ActiveSupport::Concern
|
|
19
21
|
|
|
20
|
-
attr_accessor :new_record
|
|
22
|
+
attr_accessor :new_record, :destroyed
|
|
21
23
|
alias new_record? new_record
|
|
24
|
+
alias destroyed? destroyed
|
|
22
25
|
|
|
23
26
|
# @private
|
|
24
27
|
UNIX_EPOCH_DATE = Date.new(1970, 1, 1).freeze
|
|
@@ -165,7 +168,7 @@ module Dynamoid
|
|
|
165
168
|
|
|
166
169
|
# Create a model.
|
|
167
170
|
#
|
|
168
|
-
# Initializes a new model and immediately saves it
|
|
171
|
+
# Initializes a new model and immediately saves it into DynamoDB.
|
|
169
172
|
#
|
|
170
173
|
# User.create(first_name: 'Mark', last_name: 'Tyler')
|
|
171
174
|
#
|
|
@@ -173,7 +176,8 @@ module Dynamoid
|
|
|
173
176
|
#
|
|
174
177
|
# User.create([{ first_name: 'Alice' }, { first_name: 'Bob' }])
|
|
175
178
|
#
|
|
176
|
-
#
|
|
179
|
+
# Instantiates a model and pass it into an optional block to set other
|
|
180
|
+
# attributes.
|
|
177
181
|
#
|
|
178
182
|
# User.create(first_name: 'Mark') do |u|
|
|
179
183
|
# u.age = 21
|
|
@@ -181,7 +185,10 @@ module Dynamoid
|
|
|
181
185
|
#
|
|
182
186
|
# Validates model and runs callbacks.
|
|
183
187
|
#
|
|
184
|
-
#
|
|
188
|
+
# Raises +Dynamoid::Errors::MissingRangeKey+ if a sort key is required
|
|
189
|
+
# but not specified or has value +nil+.
|
|
190
|
+
#
|
|
191
|
+
# @param attrs [Hash|Array<Hash>] Attributes of a model
|
|
185
192
|
# @param block [Proc] Block to process a document after initialization
|
|
186
193
|
# @return [Dynamoid::Document] The created document
|
|
187
194
|
# @since 0.2.0
|
|
@@ -195,12 +202,31 @@ module Dynamoid
|
|
|
195
202
|
|
|
196
203
|
# Create a model.
|
|
197
204
|
#
|
|
198
|
-
# Initializes a new object and immediately saves it
|
|
205
|
+
# Initializes a new object and immediately saves it into DynamoDB.
|
|
206
|
+
#
|
|
207
|
+
# User.create!(first_name: 'Mark', last_name: 'Tyler')
|
|
208
|
+
#
|
|
199
209
|
# Raises an exception +Dynamoid::Errors::DocumentNotValid+ if validation
|
|
200
|
-
# failed.
|
|
210
|
+
# failed.
|
|
211
|
+
#
|
|
212
|
+
# Accepts both Hash and Array of Hashes and can create several
|
|
201
213
|
# models.
|
|
202
214
|
#
|
|
203
|
-
#
|
|
215
|
+
# User.create!([{ first_name: 'Alice' }, { first_name: 'Bob' }])
|
|
216
|
+
#
|
|
217
|
+
# Instantiates a model and pass it into an optional block to set other
|
|
218
|
+
# attributes.
|
|
219
|
+
#
|
|
220
|
+
# User.create!(first_name: 'Mark') do |u|
|
|
221
|
+
# u.age = 21
|
|
222
|
+
# end
|
|
223
|
+
#
|
|
224
|
+
# Validates model and runs callbacks.
|
|
225
|
+
#
|
|
226
|
+
# Raises +Dynamoid::Errors::MissingRangeKey+ if a sort key is required
|
|
227
|
+
# but not specified or has value +nil+.
|
|
228
|
+
#
|
|
229
|
+
# @param attrs [Hash|Array<Hash>] Attributes with which to create the object.
|
|
204
230
|
# @param block [Proc] Block to process a document after initialization
|
|
205
231
|
# @return [Dynamoid::Document] The created document
|
|
206
232
|
# @since 0.2.0
|
|
@@ -270,7 +296,7 @@ module Dynamoid
|
|
|
270
296
|
# meets the specified conditions. Conditions can be specified as a +Hash+
|
|
271
297
|
# with +:if+ key:
|
|
272
298
|
#
|
|
273
|
-
# User.update_fields('1', { age: 26 }, if: { version: 1 })
|
|
299
|
+
# User.update_fields('1', { age: 26 }, { if: { version: 1 } })
|
|
274
300
|
#
|
|
275
301
|
# Here +User+ model has an integer +version+ field and the document will
|
|
276
302
|
# be updated only if the +version+ attribute currently has value 1.
|
|
@@ -278,16 +304,29 @@ module Dynamoid
|
|
|
278
304
|
# If a document with specified hash and range keys doesn't exist or
|
|
279
305
|
# conditions were specified and failed the method call returns +nil+.
|
|
280
306
|
#
|
|
307
|
+
# To check if some attribute (or attributes) isn't stored in a DynamoDB
|
|
308
|
+
# item (e.g. it wasn't set explicitly) there is another condition -
|
|
309
|
+
# +unless_exists+:
|
|
310
|
+
#
|
|
311
|
+
# user = User.create(name: 'Tylor')
|
|
312
|
+
# User.update_fields(user.id, { age: 18 }, { unless_exists: [:age] })
|
|
313
|
+
#
|
|
281
314
|
# +update_fields+ uses the +UpdateItem+ operation so it saves changes and
|
|
282
315
|
# loads an updated document back with one HTTP request.
|
|
283
316
|
#
|
|
284
317
|
# Raises a +Dynamoid::Errors::UnknownAttribute+ exception if any of the
|
|
285
318
|
# attributes is not on the model
|
|
286
319
|
#
|
|
320
|
+
# Raises +Dynamoid::Errors::MissingHashKey+ if a partition key has value
|
|
321
|
+
# +nil+ and +Dynamoid::Errors::MissingRangeKey+ if a sort key is required
|
|
322
|
+
# but has value +nil+.
|
|
323
|
+
#
|
|
287
324
|
# @param hash_key_value [Scalar value] hash key
|
|
288
325
|
# @param range_key_value [Scalar value] range key (optional)
|
|
289
326
|
# @param attrs [Hash]
|
|
290
327
|
# @param conditions [Hash] (optional)
|
|
328
|
+
# @option conditions [Hash] :if conditions on attribute values
|
|
329
|
+
# @option conditions [Hash] :unless_exists conditions on attributes presence
|
|
291
330
|
# @return [Dynamoid::Document|nil] Updated document
|
|
292
331
|
def update_fields(hash_key_value, range_key_value = nil, attrs = {}, conditions = {})
|
|
293
332
|
optional_params = [range_key_value, attrs, conditions].compact
|
|
@@ -322,23 +361,36 @@ module Dynamoid
|
|
|
322
361
|
# meets the specified conditions. Conditions can be specified as a +Hash+
|
|
323
362
|
# with +:if+ key:
|
|
324
363
|
#
|
|
325
|
-
# User.upsert('1', { age: 26 }, if: { version: 1 })
|
|
364
|
+
# User.upsert('1', { age: 26 }, { if: { version: 1 } })
|
|
326
365
|
#
|
|
327
366
|
# Here +User+ model has an integer +version+ field and the document will
|
|
328
367
|
# be updated only if the +version+ attribute currently has value 1.
|
|
329
368
|
#
|
|
369
|
+
# To check if some attribute (or attributes) isn't stored in a DynamoDB
|
|
370
|
+
# item (e.g. it wasn't set explicitly) there is another condition -
|
|
371
|
+
# +unless_exists+:
|
|
372
|
+
#
|
|
373
|
+
# user = User.create(name: 'Tylor')
|
|
374
|
+
# User.upsert(user.id, { age: 18 }, { unless_exists: [:age] })
|
|
375
|
+
#
|
|
330
376
|
# If conditions were specified and failed the method call returns +nil+.
|
|
331
377
|
#
|
|
332
378
|
# +upsert+ uses the +UpdateItem+ operation so it saves changes and loads
|
|
333
379
|
# an updated document back with one HTTP request.
|
|
334
380
|
#
|
|
335
381
|
# Raises a +Dynamoid::Errors::UnknownAttribute+ exception if any of the
|
|
336
|
-
# attributes is not
|
|
382
|
+
# attributes is not declared in the model class.
|
|
383
|
+
#
|
|
384
|
+
# Raises +Dynamoid::Errors::MissingHashKey+ if a partition key has value
|
|
385
|
+
# +nil+ and raises +Dynamoid::Errors::MissingRangeKey+ if a sort key is
|
|
386
|
+
# required but has value +nil+.
|
|
337
387
|
#
|
|
338
388
|
# @param hash_key_value [Scalar value] hash key
|
|
339
389
|
# @param range_key_value [Scalar value] range key (optional)
|
|
340
390
|
# @param attrs [Hash]
|
|
341
391
|
# @param conditions [Hash] (optional)
|
|
392
|
+
# @option conditions [Hash] :if conditions on attribute values
|
|
393
|
+
# @option conditions [Hash] :unless_exists conditions on attributes presence
|
|
342
394
|
# @return [Dynamoid::Document|nil] Updated document
|
|
343
395
|
def upsert(hash_key_value, range_key_value = nil, attrs = {}, conditions = {})
|
|
344
396
|
optional_params = [range_key_value, attrs, conditions].compact
|
|
@@ -378,28 +430,22 @@ module Dynamoid
|
|
|
378
430
|
# Doesn't run validations and callbacks. Doesn't update +created_at+ and
|
|
379
431
|
# +updated_at+ as well.
|
|
380
432
|
#
|
|
433
|
+
# When `:touch` option is passed the timestamp columns are updating. If
|
|
434
|
+
# attribute names are passed, they are updated along with updated_at
|
|
435
|
+
# attribute:
|
|
436
|
+
#
|
|
437
|
+
# User.inc('1', age: 2, touch: true)
|
|
438
|
+
# User.inc('1', age: 2, touch: :viewed_at)
|
|
439
|
+
# User.inc('1', age: 2, touch: [:viewed_at, :accessed_at])
|
|
440
|
+
#
|
|
381
441
|
# @param hash_key_value [Scalar value] hash key
|
|
382
442
|
# @param range_key_value [Scalar value] range key (optional)
|
|
383
443
|
# @param counters [Hash] value to increase by
|
|
444
|
+
# @option counters [true | Symbol | Array<Symbol>] :touch to update update_at attribute and optionally the specified ones
|
|
384
445
|
# @return [Model class] self
|
|
385
446
|
def inc(hash_key_value, range_key_value = nil, counters)
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
value_dumped = Dumping.dump_field(value_casted, attributes[range_key])
|
|
389
|
-
{ range_key: value_dumped }
|
|
390
|
-
else
|
|
391
|
-
{}
|
|
392
|
-
end
|
|
393
|
-
|
|
394
|
-
Dynamoid.adapter.update_item(table_name, hash_key_value, options) do |t|
|
|
395
|
-
counters.each do |k, v|
|
|
396
|
-
value_casted = TypeCasting.cast_field(v, attributes[k])
|
|
397
|
-
value_dumped = Dumping.dump_field(value_casted, attributes[k])
|
|
398
|
-
|
|
399
|
-
t.add(k => value_dumped)
|
|
400
|
-
end
|
|
401
|
-
end
|
|
402
|
-
|
|
447
|
+
# It's similar to Rails' #update_counters.
|
|
448
|
+
Inc.call(self, hash_key_value, range_key_value, counters)
|
|
403
449
|
self
|
|
404
450
|
end
|
|
405
451
|
end
|
|
@@ -410,17 +456,43 @@ module Dynamoid
|
|
|
410
456
|
#
|
|
411
457
|
# post.touch
|
|
412
458
|
#
|
|
413
|
-
# Can update
|
|
459
|
+
# Can update other fields in addition with the same timestamp if their
|
|
460
|
+
# names passed as arguments.
|
|
414
461
|
#
|
|
415
|
-
# user.touch(:last_login_at)
|
|
462
|
+
# user.touch(:last_login_at, :viewed_at)
|
|
416
463
|
#
|
|
417
|
-
#
|
|
464
|
+
# Some specific value can be used to save:
|
|
465
|
+
#
|
|
466
|
+
# user.touch(time: 1.hour.ago)
|
|
467
|
+
#
|
|
468
|
+
# No validation is performed and only +after_touch+ callback is called.
|
|
469
|
+
#
|
|
470
|
+
# The method must be used on a persisted object, otherwise
|
|
471
|
+
# +Dynamoid::Errors::Error+ will be thrown.
|
|
472
|
+
#
|
|
473
|
+
# @param names [*Symbol] a list of attribute names to update (optional)
|
|
474
|
+
# @param time [Time] datetime value that can be used instead of the current time (optional)
|
|
418
475
|
# @return [Dynamoid::Document] self
|
|
419
|
-
def touch(
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
476
|
+
def touch(*names, time: nil)
|
|
477
|
+
if new_record?
|
|
478
|
+
raise Dynamoid::Errors::Error, 'cannot touch on a new or destroyed record object'
|
|
479
|
+
end
|
|
480
|
+
|
|
481
|
+
time_to_assign = time || DateTime.now
|
|
482
|
+
|
|
483
|
+
self.updated_at = time_to_assign
|
|
484
|
+
names.each do |name|
|
|
485
|
+
attributes[name] = time_to_assign
|
|
486
|
+
end
|
|
487
|
+
|
|
488
|
+
attribute_names = names.map(&:to_sym) + [:updated_at]
|
|
489
|
+
attributes_with_values = attributes.slice(*attribute_names)
|
|
490
|
+
|
|
491
|
+
run_callbacks :touch do
|
|
492
|
+
self.class.update_fields(hash_key, range_value, attributes_with_values)
|
|
493
|
+
clear_attribute_changes(attribute_names.map(&:to_s))
|
|
494
|
+
end
|
|
495
|
+
|
|
424
496
|
self
|
|
425
497
|
end
|
|
426
498
|
|
|
@@ -456,7 +528,7 @@ module Dynamoid
|
|
|
456
528
|
#
|
|
457
529
|
# +save+ by default sets timestamps attributes - +created_at+ and
|
|
458
530
|
# +updated_at+ when creates new model and updates +updated_at+ attribute
|
|
459
|
-
# when
|
|
531
|
+
# when updates already existing one.
|
|
460
532
|
#
|
|
461
533
|
# Changing +updated_at+ attribute at updating a model can be skipped with
|
|
462
534
|
# +touch: false+ option:
|
|
@@ -466,7 +538,8 @@ module Dynamoid
|
|
|
466
538
|
# If a model is new and hash key (+id+ by default) is not assigned yet
|
|
467
539
|
# it was assigned implicitly with random UUID value.
|
|
468
540
|
#
|
|
469
|
-
# If +lock_version+ attribute is declared it will be incremented. If it's
|
|
541
|
+
# If +lock_version+ attribute is declared it will be incremented. If it's
|
|
542
|
+
# blank then it will be initialized with 1.
|
|
470
543
|
#
|
|
471
544
|
# +save+ method call raises +Dynamoid::Errors::RecordNotUnique+ exception
|
|
472
545
|
# if primary key (hash key + optional range key) already exists in a
|
|
@@ -477,6 +550,11 @@ module Dynamoid
|
|
|
477
550
|
# already changed concurrently and +lock_version+ was consequently
|
|
478
551
|
# increased.
|
|
479
552
|
#
|
|
553
|
+
# Raises +Dynamoid::Errors::MissingHashKey+ if a model is already persisted
|
|
554
|
+
# and a partition key has value +nil+ and raises
|
|
555
|
+
# +Dynamoid::Errors::MissingRangeKey+ if a sort key is required but has
|
|
556
|
+
# value +nil+.
|
|
557
|
+
#
|
|
480
558
|
# When a table is not created yet the first +save+ method call will create
|
|
481
559
|
# a table. It's useful in test environment to avoid explicit table
|
|
482
560
|
# creation.
|
|
@@ -487,21 +565,102 @@ module Dynamoid
|
|
|
487
565
|
# @return [true|false] Whether saving successful or not
|
|
488
566
|
# @since 0.2.0
|
|
489
567
|
def save(options = {})
|
|
490
|
-
|
|
568
|
+
if Dynamoid.config.create_table_on_save
|
|
569
|
+
self.class.create_table(sync: true)
|
|
570
|
+
end
|
|
491
571
|
|
|
492
|
-
|
|
572
|
+
create_or_update = new_record? ? :create : :update
|
|
573
|
+
|
|
574
|
+
run_callbacks(:save) do
|
|
575
|
+
run_callbacks(create_or_update) do
|
|
576
|
+
Save.call(self, touch: options[:touch])
|
|
577
|
+
end
|
|
578
|
+
end
|
|
579
|
+
end
|
|
580
|
+
|
|
581
|
+
# Create new model or persist changes.
|
|
582
|
+
#
|
|
583
|
+
# Run the validation and callbacks. Raises
|
|
584
|
+
# +Dynamoid::Errors::DocumentNotValid+ is validation fails.
|
|
585
|
+
#
|
|
586
|
+
# user = User.create
|
|
587
|
+
#
|
|
588
|
+
# user.age = 26
|
|
589
|
+
# user.save! # => user
|
|
590
|
+
#
|
|
591
|
+
# Validation can be skipped with +validate: false+ option:
|
|
592
|
+
#
|
|
593
|
+
# user = User.new(age: -1)
|
|
594
|
+
# user.save!(validate: false) # => user
|
|
595
|
+
#
|
|
596
|
+
# +save!+ by default sets timestamps attributes - +created_at+ and
|
|
597
|
+
# +updated_at+ when creates new model and updates +updated_at+ attribute
|
|
598
|
+
# when updates already existing one.
|
|
599
|
+
#
|
|
600
|
+
# Changing +updated_at+ attribute at updating a model can be skipped with
|
|
601
|
+
# +touch: false+ option:
|
|
602
|
+
#
|
|
603
|
+
# user.save!(touch: false)
|
|
604
|
+
#
|
|
605
|
+
# If a model is new and hash key (+id+ by default) is not assigned yet
|
|
606
|
+
# it was assigned implicitly with random UUID value.
|
|
607
|
+
#
|
|
608
|
+
# If +lock_version+ attribute is declared it will be incremented. If it's
|
|
609
|
+
# blank then it will be initialized with 1.
|
|
610
|
+
#
|
|
611
|
+
# +save!+ method call raises +Dynamoid::Errors::RecordNotUnique+ exception
|
|
612
|
+
# if primary key (hash key + optional range key) already exists in a
|
|
613
|
+
# table.
|
|
614
|
+
#
|
|
615
|
+
# +save!+ method call raises +Dynamoid::Errors::StaleObjectError+ exception
|
|
616
|
+
# if there is +lock_version+ attribute and the document in a table was
|
|
617
|
+
# already changed concurrently and +lock_version+ was consequently
|
|
618
|
+
# increased.
|
|
619
|
+
#
|
|
620
|
+
# +save!+ method call raises +Dynamoid::Errors::RecordNotSaved+ exception
|
|
621
|
+
# if some callback aborted execution.
|
|
622
|
+
#
|
|
623
|
+
# Raises +Dynamoid::Errors::MissingHashKey+ if a model is already persisted
|
|
624
|
+
# and a partition key has value +nil+ and raises
|
|
625
|
+
# +Dynamoid::Errors::MissingRangeKey+ if a sort key is required but has
|
|
626
|
+
# value +nil+.
|
|
627
|
+
#
|
|
628
|
+
# When a table is not created yet the first +save!+ method call will create
|
|
629
|
+
# a table. It's useful in test environment to avoid explicit table
|
|
630
|
+
# creation.
|
|
631
|
+
#
|
|
632
|
+
# @param options [Hash] (optional)
|
|
633
|
+
# @option options [true|false] :validate validate a model or not - +true+ by default (optional)
|
|
634
|
+
# @option options [true|false] :touch update tiemstamps fields or not - +true+ by default (optional)
|
|
635
|
+
# @return [true|false] Whether saving successful or not
|
|
636
|
+
def save!(options = {})
|
|
637
|
+
# validation is handled in the Validation module
|
|
638
|
+
|
|
639
|
+
if Dynamoid.config.create_table_on_save
|
|
640
|
+
self.class.create_table(sync: true)
|
|
641
|
+
end
|
|
493
642
|
|
|
494
643
|
create_or_update = new_record? ? :create : :update
|
|
644
|
+
aborted = true
|
|
495
645
|
|
|
496
|
-
run_callbacks(
|
|
497
|
-
run_callbacks(
|
|
498
|
-
|
|
646
|
+
run_callbacks(:save) do
|
|
647
|
+
run_callbacks(create_or_update) do
|
|
648
|
+
aborted = false
|
|
649
|
+
Save.call(self, touch: options[:touch])
|
|
499
650
|
end
|
|
500
651
|
end
|
|
652
|
+
|
|
653
|
+
if aborted
|
|
654
|
+
raise Dynamoid::Errors::RecordNotSaved, self
|
|
655
|
+
end
|
|
656
|
+
|
|
657
|
+
self
|
|
501
658
|
end
|
|
502
659
|
|
|
503
660
|
# Update multiple attributes at once, saving the object once the updates
|
|
504
|
-
# are complete.
|
|
661
|
+
# are complete.
|
|
662
|
+
#
|
|
663
|
+
# Returns +true+ if saving is successful and +false+
|
|
505
664
|
# otherwise.
|
|
506
665
|
#
|
|
507
666
|
# user.update_attributes(age: 27, last_name: 'Tylor')
|
|
@@ -509,6 +668,10 @@ module Dynamoid
|
|
|
509
668
|
# Raises a +Dynamoid::Errors::UnknownAttribute+ exception if any of the
|
|
510
669
|
# attributes is not on the model
|
|
511
670
|
#
|
|
671
|
+
# Raises +Dynamoid::Errors::MissingHashKey+ if a partition key has value
|
|
672
|
+
# +nil+ and raises +Dynamoid::Errors::MissingRangeKey+ if a sort key is
|
|
673
|
+
# required but has value +nil+.
|
|
674
|
+
#
|
|
512
675
|
# @param attributes [Hash] a hash of attributes to update
|
|
513
676
|
# @return [true|false] Whether updating successful or not
|
|
514
677
|
# @since 0.2.0
|
|
@@ -528,6 +691,10 @@ module Dynamoid
|
|
|
528
691
|
# Raises a +Dynamoid::Errors::UnknownAttribute+ exception if any of the
|
|
529
692
|
# attributes is not on the model
|
|
530
693
|
#
|
|
694
|
+
# Raises +Dynamoid::Errors::MissingHashKey+ if a partition key has value
|
|
695
|
+
# +nil+ and raises +Dynamoid::Errors::MissingRangeKey+ if a sort key is
|
|
696
|
+
# required but has value +nil+.
|
|
697
|
+
#
|
|
531
698
|
# @param attributes [Hash] a hash of attributes to update
|
|
532
699
|
def update_attributes!(attributes)
|
|
533
700
|
attributes.each { |attribute, value| write_attribute(attribute, value) }
|
|
@@ -540,6 +707,8 @@ module Dynamoid
|
|
|
540
707
|
#
|
|
541
708
|
# user.update_attribute(:last_name, 'Tylor')
|
|
542
709
|
#
|
|
710
|
+
# Validation is skipped.
|
|
711
|
+
#
|
|
543
712
|
# Raises a +Dynamoid::Errors::UnknownAttribute+ exception if any of the
|
|
544
713
|
# attributes is not on the model
|
|
545
714
|
#
|
|
@@ -550,21 +719,17 @@ module Dynamoid
|
|
|
550
719
|
# @since 0.2.0
|
|
551
720
|
def update_attribute(attribute, value)
|
|
552
721
|
# final implementation is in the Dynamoid::Validation module
|
|
553
|
-
write_attribute(attribute, value)
|
|
554
|
-
save
|
|
555
|
-
self
|
|
556
722
|
end
|
|
557
723
|
|
|
558
724
|
# Update a model.
|
|
559
725
|
#
|
|
560
|
-
#
|
|
726
|
+
# Doesn't run validation. Runs only +update+ callbacks. Reloads all attribute values.
|
|
561
727
|
#
|
|
562
728
|
# Accepts mandatory block in order to specify operations which will modify
|
|
563
729
|
# attributes. Supports following operations: +add+, +delete+ and +set+.
|
|
564
730
|
#
|
|
565
731
|
# Operation +add+ just adds a value for numeric attributes and join
|
|
566
|
-
# collections if attribute is a
|
|
567
|
-
# +map+).
|
|
732
|
+
# collections if attribute is a set.
|
|
568
733
|
#
|
|
569
734
|
# user.update! do |t|
|
|
570
735
|
# t.add(age: 1, followers_count: 5)
|
|
@@ -589,7 +754,7 @@ module Dynamoid
|
|
|
589
754
|
# {parameter}[https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/LegacyConditionalParameters.AttributeUpdates.html]
|
|
590
755
|
# of +UpdateItem+ operation.
|
|
591
756
|
#
|
|
592
|
-
# It's
|
|
757
|
+
# It's atomic operations. So adding or deleting elements in a collection
|
|
593
758
|
# or incrementing or decrementing a numeric field is atomic and does not
|
|
594
759
|
# interfere with other write requests.
|
|
595
760
|
#
|
|
@@ -599,6 +764,15 @@ module Dynamoid
|
|
|
599
764
|
# t.add(age: 1)
|
|
600
765
|
# end
|
|
601
766
|
#
|
|
767
|
+
# To check if some attribute (or attributes) isn't stored in a DynamoDB
|
|
768
|
+
# item (e.g. it wasn't set explicitly) there is another condition -
|
|
769
|
+
# +unless_exists+:
|
|
770
|
+
#
|
|
771
|
+
# user = User.create(name: 'Tylor')
|
|
772
|
+
# user.update!(unless_exists: [:age]) do |t|
|
|
773
|
+
# t.set(age: 18)
|
|
774
|
+
# end
|
|
775
|
+
#
|
|
602
776
|
# If a document doesn't meet conditions it raises
|
|
603
777
|
# +Dynamoid::Errors::StaleObjectError+ exception.
|
|
604
778
|
#
|
|
@@ -620,21 +794,33 @@ module Dynamoid
|
|
|
620
794
|
|
|
621
795
|
begin
|
|
622
796
|
table_name = self.class.table_name
|
|
797
|
+
partition_key_dumped = Dumping.dump_field(hash_key, self.class.attributes[self.class.hash_key])
|
|
798
|
+
conditions = conditions.dup
|
|
799
|
+
conditions[:if] ||= {}
|
|
800
|
+
conditions[:if][self.class.hash_key] = partition_key_dumped
|
|
801
|
+
if self.class.range_key
|
|
802
|
+
sort_key_dumped = Dumping.dump_field(range_value, self.class.attributes[self.class.range_key])
|
|
803
|
+
conditions[:if][self.class.range_key] = sort_key_dumped
|
|
804
|
+
end
|
|
805
|
+
|
|
623
806
|
update_item_options = options.merge(conditions: conditions)
|
|
624
807
|
|
|
625
|
-
new_attrs = Dynamoid.adapter.update_item(table_name,
|
|
626
|
-
|
|
808
|
+
new_attrs = Dynamoid.adapter.update_item(table_name, partition_key_dumped, update_item_options) do |t|
|
|
809
|
+
item_updater = ItemUpdaterWithDumping.new(self.class, t)
|
|
810
|
+
|
|
811
|
+
item_updater.add(lock_version: 1) if self.class.attributes[:lock_version]
|
|
627
812
|
|
|
628
813
|
if self.class.timestamps_enabled?
|
|
629
|
-
|
|
630
|
-
time_now_dumped = Dumping.dump_field(time_now, self.class.attributes[:updated_at])
|
|
631
|
-
t.set(updated_at: time_now_dumped)
|
|
814
|
+
item_updater.set(updated_at: DateTime.now.in_time_zone(Time.zone))
|
|
632
815
|
end
|
|
633
816
|
|
|
634
817
|
yield t
|
|
635
818
|
end
|
|
636
819
|
load(Undumping.undump_attributes(new_attrs, self.class.attributes))
|
|
637
820
|
rescue Dynamoid::Errors::ConditionalCheckFailedException
|
|
821
|
+
# exception may be raised either because of failed user provided conditions
|
|
822
|
+
# or because of conditions on partition and sort keys. We cannot
|
|
823
|
+
# distinguish these two cases.
|
|
638
824
|
raise Dynamoid::Errors::StaleObjectError.new(self, 'update')
|
|
639
825
|
end
|
|
640
826
|
end
|
|
@@ -644,14 +830,13 @@ module Dynamoid
|
|
|
644
830
|
|
|
645
831
|
# Update a model.
|
|
646
832
|
#
|
|
647
|
-
#
|
|
833
|
+
# Doesn't run validation. Runs only +update+ callbacks. Reloads all attribute values.
|
|
648
834
|
#
|
|
649
835
|
# Accepts mandatory block in order to specify operations which will modify
|
|
650
836
|
# attributes. Supports following operations: +add+, +delete+ and +set+.
|
|
651
837
|
#
|
|
652
838
|
# Operation +add+ just adds a value for numeric attributes and join
|
|
653
|
-
# collections if attribute is a
|
|
654
|
-
# +map+).
|
|
839
|
+
# collections if attribute is a set.
|
|
655
840
|
#
|
|
656
841
|
# user.update do |t|
|
|
657
842
|
# t.add(age: 1, followers_count: 5)
|
|
@@ -695,6 +880,15 @@ module Dynamoid
|
|
|
695
880
|
# t.add(age: 1)
|
|
696
881
|
# end
|
|
697
882
|
#
|
|
883
|
+
# To check if some attribute (or attributes) isn't stored in a DynamoDB
|
|
884
|
+
# item (e.g. it wasn't set explicitly) there is another condition -
|
|
885
|
+
# +unless_exists+:
|
|
886
|
+
#
|
|
887
|
+
# user = User.create(name: 'Tylor')
|
|
888
|
+
# user.update(unless_exists: [:age]) do |t|
|
|
889
|
+
# t.set(age: 18)
|
|
890
|
+
# end
|
|
891
|
+
#
|
|
698
892
|
# If a document doesn't meet conditions it just returns +false+. Otherwise it returns +true+.
|
|
699
893
|
#
|
|
700
894
|
# It will increment the +lock_version+ attribute if a table has the column,
|
|
@@ -736,14 +930,32 @@ module Dynamoid
|
|
|
736
930
|
# user.increment!(:followers_count)
|
|
737
931
|
# user.increment!(:followers_count, 2)
|
|
738
932
|
#
|
|
739
|
-
#
|
|
933
|
+
# Only `attribute` is saved. The model itself is not saved. So any other
|
|
934
|
+
# modified attributes will still be dirty. Validations and callbacks are
|
|
935
|
+
# skipped.
|
|
936
|
+
#
|
|
937
|
+
# When `:touch` option is passed the timestamp columns are updating. If
|
|
938
|
+
# attribute names are passed, they are updated along with updated_at
|
|
939
|
+
# attribute:
|
|
940
|
+
#
|
|
941
|
+
# user.increment!(:followers_count, touch: true)
|
|
942
|
+
# user.increment!(:followers_count, touch: :viewed_at)
|
|
943
|
+
# user.increment!(:followers_count, touch: [:viewed_at, :accessed_at])
|
|
740
944
|
#
|
|
741
945
|
# @param attribute [Symbol] attribute name
|
|
742
946
|
# @param by [Numeric] value to add (optional)
|
|
743
|
-
# @
|
|
744
|
-
|
|
947
|
+
# @param touch [true | Symbol | Array<Symbol>] to update update_at attribute and optionally the specified ones
|
|
948
|
+
# @return [Dynamoid::Document] self
|
|
949
|
+
def increment!(attribute, by = 1, touch: nil)
|
|
745
950
|
increment(attribute, by)
|
|
746
|
-
|
|
951
|
+
change = read_attribute(attribute) - (attribute_was(attribute) || 0)
|
|
952
|
+
|
|
953
|
+
run_callbacks :touch do
|
|
954
|
+
self.class.inc(hash_key, range_value, attribute => change, touch: touch)
|
|
955
|
+
clear_attribute_changes(attribute)
|
|
956
|
+
end
|
|
957
|
+
|
|
958
|
+
self
|
|
747
959
|
end
|
|
748
960
|
|
|
749
961
|
# Change numeric attribute value.
|
|
@@ -758,9 +970,7 @@ module Dynamoid
|
|
|
758
970
|
# @param by [Numeric] value to subtract (optional)
|
|
759
971
|
# @return [Dynamoid::Document] self
|
|
760
972
|
def decrement(attribute, by = 1)
|
|
761
|
-
|
|
762
|
-
self[attribute] -= by
|
|
763
|
-
self
|
|
973
|
+
increment(attribute, -by)
|
|
764
974
|
end
|
|
765
975
|
|
|
766
976
|
# Change numeric attribute value and save a model.
|
|
@@ -771,14 +981,24 @@ module Dynamoid
|
|
|
771
981
|
# user.decrement!(:followers_count)
|
|
772
982
|
# user.decrement!(:followers_count, 2)
|
|
773
983
|
#
|
|
774
|
-
#
|
|
984
|
+
# Only `attribute` is saved. The model itself is not saved. So any other
|
|
985
|
+
# modified attributes will still be dirty. Validations and callbacks are
|
|
986
|
+
# skipped.
|
|
987
|
+
#
|
|
988
|
+
# When `:touch` option is passed the timestamp columns are updating. If
|
|
989
|
+
# attribute names are passed, they are updated along with updated_at
|
|
990
|
+
# attribute:
|
|
991
|
+
#
|
|
992
|
+
# user.decrement!(:followers_count, touch: true)
|
|
993
|
+
# user.decrement!(:followers_count, touch: :viewed_at)
|
|
994
|
+
# user.decrement!(:followers_count, touch: [:viewed_at, :accessed_at])
|
|
775
995
|
#
|
|
776
996
|
# @param attribute [Symbol] attribute name
|
|
777
997
|
# @param by [Numeric] value to subtract (optional)
|
|
778
|
-
# @
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
998
|
+
# @param touch [true | Symbol | Array<Symbol>] to update update_at attribute and optionally the specified ones
|
|
999
|
+
# @return [Dynamoid::Document] self
|
|
1000
|
+
def decrement!(attribute, by = 1, touch: nil)
|
|
1001
|
+
increment!(attribute, -by, touch: touch)
|
|
782
1002
|
end
|
|
783
1003
|
|
|
784
1004
|
# Delete a model.
|
|
@@ -790,6 +1010,10 @@ module Dynamoid
|
|
|
790
1010
|
#
|
|
791
1011
|
# Returns +self+ if deleted successfully and +false+ otherwise.
|
|
792
1012
|
#
|
|
1013
|
+
# Raises +Dynamoid::Errors::MissingHashKey+ if a partition key has value
|
|
1014
|
+
# +nil+ and raises +Dynamoid::Errors::MissingRangeKey+ if a sort key is
|
|
1015
|
+
# required but has value +nil+.
|
|
1016
|
+
#
|
|
793
1017
|
# @return [Dynamoid::Document|false] whether deleted successfully
|
|
794
1018
|
# @since 0.2.0
|
|
795
1019
|
def destroy
|
|
@@ -811,6 +1035,10 @@ module Dynamoid
|
|
|
811
1035
|
#
|
|
812
1036
|
# Raises +Dynamoid::Errors::RecordNotDestroyed+ exception if model deleting
|
|
813
1037
|
# failed.
|
|
1038
|
+
#
|
|
1039
|
+
# Raises +Dynamoid::Errors::MissingHashKey+ if a partition key has value
|
|
1040
|
+
# +nil+ and raises +Dynamoid::Errors::MissingRangeKey+ if a sort key is
|
|
1041
|
+
# required but has value +nil+.
|
|
814
1042
|
def destroy!
|
|
815
1043
|
destroy || (raise Dynamoid::Errors::RecordNotDestroyed, self)
|
|
816
1044
|
end
|
|
@@ -823,10 +1051,18 @@ module Dynamoid
|
|
|
823
1051
|
# Raises +Dynamoid::Errors::StaleObjectError+ exception if cannot delete a
|
|
824
1052
|
# model.
|
|
825
1053
|
#
|
|
1054
|
+
# Raises +Dynamoid::Errors::MissingHashKey+ if a partition key has value
|
|
1055
|
+
# +nil+ and raises +Dynamoid::Errors::MissingRangeKey+ if a sort key is
|
|
1056
|
+
# required but has value +nil+.
|
|
1057
|
+
#
|
|
826
1058
|
# @return [Dynamoid::Document] self
|
|
827
1059
|
# @since 0.2.0
|
|
828
1060
|
def delete
|
|
1061
|
+
raise Dynamoid::Errors::MissingHashKey if hash_key.nil?
|
|
1062
|
+
raise Dynamoid::Errors::MissingRangeKey if self.class.range_key? && range_value.nil?
|
|
1063
|
+
|
|
829
1064
|
options = range_key ? { range_key: Dumping.dump_field(read_attribute(range_key), self.class.attributes[range_key]) } : {}
|
|
1065
|
+
partition_key_dumped = Dumping.dump_field(hash_key, self.class.attributes[self.class.hash_key])
|
|
830
1066
|
|
|
831
1067
|
# Add an optimistic locking check if the lock_version column exists
|
|
832
1068
|
if self.class.attributes[:lock_version]
|
|
@@ -842,9 +1078,9 @@ module Dynamoid
|
|
|
842
1078
|
|
|
843
1079
|
@destroyed = true
|
|
844
1080
|
|
|
845
|
-
Dynamoid.adapter.delete(self.class.table_name,
|
|
1081
|
+
Dynamoid.adapter.delete(self.class.table_name, partition_key_dumped, options)
|
|
846
1082
|
|
|
847
|
-
self.class.associations.
|
|
1083
|
+
self.class.associations.each_key do |name|
|
|
848
1084
|
send(name).disassociate_source
|
|
849
1085
|
end
|
|
850
1086
|
|