dynamoid 3.8.0 → 3.10.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 +54 -3
- data/README.md +111 -60
- data/SECURITY.md +17 -0
- data/dynamoid.gemspec +65 -0
- data/lib/dynamoid/adapter.rb +20 -13
- 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 +78 -0
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/item_updater.rb +28 -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 +38 -0
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/query.rb +46 -61
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/scan.rb +33 -27
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3.rb +116 -70
- data/lib/dynamoid/associations/belongs_to.rb +6 -6
- data/lib/dynamoid/associations.rb +1 -1
- data/lib/dynamoid/components.rb +2 -3
- data/lib/dynamoid/config/options.rb +12 -12
- data/lib/dynamoid/config.rb +1 -0
- data/lib/dynamoid/criteria/chain.rb +101 -138
- 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 +29 -0
- data/lib/dynamoid/dirty.rb +57 -57
- data/lib/dynamoid/document.rb +39 -3
- data/lib/dynamoid/dumping.rb +2 -2
- data/lib/dynamoid/errors.rb +2 -0
- data/lib/dynamoid/fields/declare.rb +6 -6
- data/lib/dynamoid/fields.rb +9 -27
- data/lib/dynamoid/finders.rb +26 -30
- data/lib/dynamoid/indexes.rb +7 -10
- data/lib/dynamoid/loadable.rb +2 -2
- data/lib/dynamoid/log/formatter.rb +19 -4
- data/lib/dynamoid/persistence/import.rb +4 -1
- data/lib/dynamoid/persistence/inc.rb +66 -0
- data/lib/dynamoid/persistence/save.rb +55 -12
- data/lib/dynamoid/persistence/update_fields.rb +2 -2
- data/lib/dynamoid/persistence/update_validations.rb +2 -2
- data/lib/dynamoid/persistence.rb +128 -48
- data/lib/dynamoid/type_casting.rb +15 -14
- data/lib/dynamoid/undumping.rb +1 -1
- data/lib/dynamoid/version.rb +1 -1
- metadata +27 -49
- data/lib/dynamoid/criteria/ignored_conditions_detector.rb +0 -41
- data/lib/dynamoid/criteria/overwritten_conditions_detector.rb +0 -40
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dynamoid
|
4
|
+
module Persistence
|
5
|
+
# @private
|
6
|
+
class Inc
|
7
|
+
def self.call(model_class, hash_key, range_key = nil, counters)
|
8
|
+
new(model_class, hash_key, range_key, counters).call
|
9
|
+
end
|
10
|
+
|
11
|
+
# rubocop:disable Style/OptionalArguments
|
12
|
+
def initialize(model_class, hash_key, range_key = nil, counters)
|
13
|
+
@model_class = model_class
|
14
|
+
@hash_key = hash_key
|
15
|
+
@range_key = range_key
|
16
|
+
@counters = counters
|
17
|
+
end
|
18
|
+
# rubocop:enable Style/OptionalArguments
|
19
|
+
|
20
|
+
def call
|
21
|
+
touch = @counters.delete(:touch)
|
22
|
+
|
23
|
+
Dynamoid.adapter.update_item(@model_class.table_name, @hash_key, update_item_options) do |t|
|
24
|
+
@counters.each do |name, value|
|
25
|
+
t.add(name => cast_and_dump_attribute_value(name, value))
|
26
|
+
end
|
27
|
+
|
28
|
+
if touch
|
29
|
+
value = DateTime.now.in_time_zone(Time.zone)
|
30
|
+
|
31
|
+
timestamp_attributes_to_touch(touch).each do |name|
|
32
|
+
t.set(name => cast_and_dump_attribute_value(name, value))
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def update_item_options
|
41
|
+
if @model_class.range_key
|
42
|
+
range_key_options = @model_class.attributes[@model_class.range_key]
|
43
|
+
value_casted = TypeCasting.cast_field(@range_key, range_key_options)
|
44
|
+
value_dumped = Dumping.dump_field(value_casted, range_key_options)
|
45
|
+
{ range_key: value_dumped }
|
46
|
+
else
|
47
|
+
{}
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def cast_and_dump_attribute_value(name, value)
|
52
|
+
value_casted = TypeCasting.cast_field(value, @model_class.attributes[name])
|
53
|
+
Dumping.dump_field(value_casted, @model_class.attributes[name])
|
54
|
+
end
|
55
|
+
|
56
|
+
def timestamp_attributes_to_touch(touch)
|
57
|
+
return [] unless touch
|
58
|
+
|
59
|
+
names = []
|
60
|
+
names << :updated_at if @model_class.timestamps_enabled?
|
61
|
+
names += Array.wrap(touch) if touch != true
|
62
|
+
names
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -4,24 +4,44 @@ module Dynamoid
|
|
4
4
|
module Persistence
|
5
5
|
# @private
|
6
6
|
class Save
|
7
|
-
def self.call(model)
|
8
|
-
new(model).call
|
7
|
+
def self.call(model, **options)
|
8
|
+
new(model, **options).call
|
9
9
|
end
|
10
10
|
|
11
|
-
def initialize(model)
|
11
|
+
def initialize(model, touch: nil)
|
12
12
|
@model = model
|
13
|
+
@touch = touch # touch=false means explicit disabling of updating the `updated_at` attribute
|
13
14
|
end
|
14
15
|
|
15
16
|
def call
|
16
17
|
@model.hash_key = SecureRandom.uuid if @model.hash_key.blank?
|
17
18
|
|
19
|
+
return true unless @model.changed?
|
20
|
+
|
21
|
+
@model.created_at ||= DateTime.now.in_time_zone(Time.zone) if @model.class.timestamps_enabled?
|
22
|
+
|
23
|
+
if @model.class.timestamps_enabled? && !@model.updated_at_changed? && !(@touch == false && @model.persisted?)
|
24
|
+
@model.updated_at = DateTime.now.in_time_zone(Time.zone)
|
25
|
+
end
|
26
|
+
|
18
27
|
# Add an optimistic locking check if the lock_version column exists
|
19
28
|
if @model.class.attributes[:lock_version]
|
20
29
|
@model.lock_version = (@model.lock_version || 0) + 1
|
21
30
|
end
|
22
31
|
|
23
|
-
|
24
|
-
|
32
|
+
if @model.new_record?
|
33
|
+
attributes_dumped = Dumping.dump_attributes(@model.attributes, @model.class.attributes)
|
34
|
+
Dynamoid.adapter.write(@model.class.table_name, attributes_dumped, conditions_for_write)
|
35
|
+
else
|
36
|
+
attributes_to_persist = @model.attributes.slice(*@model.changed.map(&:to_sym))
|
37
|
+
|
38
|
+
Dynamoid.adapter.update_item(@model.class.table_name, @model.hash_key, options_to_update_item) do |t|
|
39
|
+
attributes_to_persist.each do |name, value|
|
40
|
+
value_dumped = Dumping.dump_field(value, @model.class.attributes[name])
|
41
|
+
t.set(name => value_dumped)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
25
45
|
|
26
46
|
@model.new_record = false
|
27
47
|
true
|
@@ -48,17 +68,40 @@ module Dynamoid
|
|
48
68
|
end
|
49
69
|
|
50
70
|
# Add an optimistic locking check if the lock_version column exists
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
conditions[:if][:lock_version] = @model.changes[:lock_version][0]
|
57
|
-
end
|
71
|
+
# Uses the original lock_version value from Dirty API
|
72
|
+
# in case user changed 'lock_version' manually
|
73
|
+
if @model.class.attributes[:lock_version] && (@model.changes[:lock_version][0])
|
74
|
+
conditions[:if] ||= {}
|
75
|
+
conditions[:if][:lock_version] = @model.changes[:lock_version][0]
|
58
76
|
end
|
59
77
|
|
60
78
|
conditions
|
61
79
|
end
|
80
|
+
|
81
|
+
def options_to_update_item
|
82
|
+
options = {}
|
83
|
+
|
84
|
+
if @model.class.range_key
|
85
|
+
value_dumped = Dumping.dump_field(@model.range_value, @model.class.attributes[@model.class.range_key])
|
86
|
+
options[:range_key] = value_dumped
|
87
|
+
end
|
88
|
+
|
89
|
+
conditions = {}
|
90
|
+
conditions[:if] ||= {}
|
91
|
+
conditions[:if][@model.class.hash_key] = @model.hash_key
|
92
|
+
|
93
|
+
# Add an optimistic locking check if the lock_version column exists
|
94
|
+
# Uses the original lock_version value from Dirty API
|
95
|
+
# in case user changed 'lock_version' manually
|
96
|
+
if @model.class.attributes[:lock_version] && (@model.changes[:lock_version][0])
|
97
|
+
conditions[:if] ||= {}
|
98
|
+
conditions[:if][:lock_version] = @model.changes[:lock_version][0]
|
99
|
+
end
|
100
|
+
|
101
|
+
options[:conditions] = conditions
|
102
|
+
|
103
|
+
options
|
104
|
+
end
|
62
105
|
end
|
63
106
|
end
|
64
107
|
end
|
@@ -54,8 +54,8 @@ module Dynamoid
|
|
54
54
|
end
|
55
55
|
|
56
56
|
conditions = @conditions.deep_dup
|
57
|
-
conditions[:
|
58
|
-
conditions[:
|
57
|
+
conditions[:if] ||= {}
|
58
|
+
conditions[:if][@model_class.hash_key] = @partition_key
|
59
59
|
options[:conditions] = conditions
|
60
60
|
|
61
61
|
options
|
@@ -7,9 +7,9 @@ module Dynamoid
|
|
7
7
|
def self.validate_attributes_exist(model_class, attributes)
|
8
8
|
model_attributes = model_class.attributes.keys
|
9
9
|
|
10
|
-
attributes.
|
10
|
+
attributes.each_key do |attr_name|
|
11
11
|
unless model_attributes.include?(attr_name)
|
12
|
-
raise Dynamoid::Errors::UnknownAttribute
|
12
|
+
raise Dynamoid::Errors::UnknownAttribute, "Attribute #{attr_name} does not exist in #{model_class}"
|
13
13
|
end
|
14
14
|
end
|
15
15
|
end
|
data/lib/dynamoid/persistence.rb
CHANGED
@@ -8,6 +8,7 @@ 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'
|
12
13
|
|
13
14
|
# encoding: utf-8
|
@@ -270,7 +271,7 @@ module Dynamoid
|
|
270
271
|
# meets the specified conditions. Conditions can be specified as a +Hash+
|
271
272
|
# with +:if+ key:
|
272
273
|
#
|
273
|
-
# User.update_fields('1', { age: 26 }, if: { version: 1 })
|
274
|
+
# User.update_fields('1', { age: 26 }, { if: { version: 1 } })
|
274
275
|
#
|
275
276
|
# Here +User+ model has an integer +version+ field and the document will
|
276
277
|
# be updated only if the +version+ attribute currently has value 1.
|
@@ -278,6 +279,13 @@ module Dynamoid
|
|
278
279
|
# If a document with specified hash and range keys doesn't exist or
|
279
280
|
# conditions were specified and failed the method call returns +nil+.
|
280
281
|
#
|
282
|
+
# To check if some attribute (or attributes) isn't stored in a DynamoDB
|
283
|
+
# item (e.g. it wasn't set explicitly) there is another condition -
|
284
|
+
# +unless_exists+:
|
285
|
+
#
|
286
|
+
# user = User.create(name: 'Tylor')
|
287
|
+
# User.update_fields(user.id, { age: 18 }, { unless_exists: [:age] })
|
288
|
+
#
|
281
289
|
# +update_fields+ uses the +UpdateItem+ operation so it saves changes and
|
282
290
|
# loads an updated document back with one HTTP request.
|
283
291
|
#
|
@@ -322,11 +330,18 @@ module Dynamoid
|
|
322
330
|
# meets the specified conditions. Conditions can be specified as a +Hash+
|
323
331
|
# with +:if+ key:
|
324
332
|
#
|
325
|
-
# User.upsert('1', { age: 26 }, if: { version: 1 })
|
333
|
+
# User.upsert('1', { age: 26 }, { if: { version: 1 } })
|
326
334
|
#
|
327
335
|
# Here +User+ model has an integer +version+ field and the document will
|
328
336
|
# be updated only if the +version+ attribute currently has value 1.
|
329
337
|
#
|
338
|
+
# To check if some attribute (or attributes) isn't stored in a DynamoDB
|
339
|
+
# item (e.g. it wasn't set explicitly) there is another condition -
|
340
|
+
# +unless_exists+:
|
341
|
+
#
|
342
|
+
# user = User.create(name: 'Tylor')
|
343
|
+
# User.upsert(user.id, { age: 18 }, { unless_exists: [:age] })
|
344
|
+
#
|
330
345
|
# If conditions were specified and failed the method call returns +nil+.
|
331
346
|
#
|
332
347
|
# +upsert+ uses the +UpdateItem+ operation so it saves changes and loads
|
@@ -378,28 +393,21 @@ module Dynamoid
|
|
378
393
|
# Doesn't run validations and callbacks. Doesn't update +created_at+ and
|
379
394
|
# +updated_at+ as well.
|
380
395
|
#
|
396
|
+
# When `:touch` option is passed the timestamp columns are updating. If
|
397
|
+
# attribute names are passed, they are updated along with updated_at
|
398
|
+
# attribute:
|
399
|
+
#
|
400
|
+
# User.inc('1', age: 2, touch: true)
|
401
|
+
# User.inc('1', age: 2, touch: :viewed_at)
|
402
|
+
# User.inc('1', age: 2, touch: [:viewed_at, :accessed_at])
|
403
|
+
#
|
381
404
|
# @param hash_key_value [Scalar value] hash key
|
382
405
|
# @param range_key_value [Scalar value] range key (optional)
|
383
406
|
# @param counters [Hash] value to increase by
|
407
|
+
# @option counters [true | Symbol | Array[Symbol]] :touch to update update_at attribute and optionally the specified ones
|
384
408
|
# @return [Model class] self
|
385
409
|
def inc(hash_key_value, range_key_value = nil, counters)
|
386
|
-
|
387
|
-
value_casted = TypeCasting.cast_field(range_key_value, attributes[range_key])
|
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
|
-
|
410
|
+
Inc.call(self, hash_key_value, range_key_value, counters)
|
403
411
|
self
|
404
412
|
end
|
405
413
|
end
|
@@ -410,17 +418,43 @@ module Dynamoid
|
|
410
418
|
#
|
411
419
|
# post.touch
|
412
420
|
#
|
413
|
-
# Can update
|
421
|
+
# Can update other fields in addition with the same timestamp if their
|
422
|
+
# names passed as arguments.
|
423
|
+
#
|
424
|
+
# user.touch(:last_login_at, :viewed_at)
|
425
|
+
#
|
426
|
+
# Some specific value can be used to save:
|
427
|
+
#
|
428
|
+
# user.touch(time: 1.hour.ago)
|
414
429
|
#
|
415
|
-
#
|
430
|
+
# No validation is performed and only +after_touch+ callback is called.
|
416
431
|
#
|
417
|
-
#
|
432
|
+
# The method must be used on a persisted object, otherwise
|
433
|
+
# +Dynamoid::Errors::Error+ will be thrown.
|
434
|
+
#
|
435
|
+
# @param names [*Symbol] a list of attribute names to update (optional)
|
436
|
+
# @param time [Time] datetime value that can be used instead of the current time (optional)
|
418
437
|
# @return [Dynamoid::Document] self
|
419
|
-
def touch(
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
438
|
+
def touch(*names, time: nil)
|
439
|
+
if new_record?
|
440
|
+
raise Dynamoid::Errors::Error, 'cannot touch on a new or destroyed record object'
|
441
|
+
end
|
442
|
+
|
443
|
+
time_to_assign = time || DateTime.now
|
444
|
+
|
445
|
+
self.updated_at = time_to_assign
|
446
|
+
names.each do |name|
|
447
|
+
attributes[name] = time_to_assign
|
448
|
+
end
|
449
|
+
|
450
|
+
attribute_names = names.map(&:to_sym) + [:updated_at]
|
451
|
+
attributes_with_values = attributes.slice(*attribute_names)
|
452
|
+
|
453
|
+
run_callbacks :touch do
|
454
|
+
self.class.update_fields(hash_key, range_value, attributes_with_values)
|
455
|
+
clear_attribute_changes(attribute_names.map(&:to_s))
|
456
|
+
end
|
457
|
+
|
424
458
|
self
|
425
459
|
end
|
426
460
|
|
@@ -487,15 +521,15 @@ module Dynamoid
|
|
487
521
|
# @return [true|false] Whether saving successful or not
|
488
522
|
# @since 0.2.0
|
489
523
|
def save(options = {})
|
490
|
-
|
491
|
-
|
492
|
-
|
524
|
+
if Dynamoid.config.create_table_on_save
|
525
|
+
self.class.create_table(sync: true)
|
526
|
+
end
|
493
527
|
|
494
528
|
create_or_update = new_record? ? :create : :update
|
495
529
|
|
496
|
-
run_callbacks(
|
497
|
-
run_callbacks(
|
498
|
-
Save.call(self)
|
530
|
+
run_callbacks(:save) do
|
531
|
+
run_callbacks(create_or_update) do
|
532
|
+
Save.call(self, touch: options[:touch])
|
499
533
|
end
|
500
534
|
end
|
501
535
|
end
|
@@ -540,6 +574,8 @@ module Dynamoid
|
|
540
574
|
#
|
541
575
|
# user.update_attribute(:last_name, 'Tylor')
|
542
576
|
#
|
577
|
+
# Validation is skipped.
|
578
|
+
#
|
543
579
|
# Raises a +Dynamoid::Errors::UnknownAttribute+ exception if any of the
|
544
580
|
# attributes is not on the model
|
545
581
|
#
|
@@ -557,7 +593,7 @@ module Dynamoid
|
|
557
593
|
|
558
594
|
# Update a model.
|
559
595
|
#
|
560
|
-
#
|
596
|
+
# Doesn't run validation. Runs only +update+ callbacks. Reloads all attribute values.
|
561
597
|
#
|
562
598
|
# Accepts mandatory block in order to specify operations which will modify
|
563
599
|
# attributes. Supports following operations: +add+, +delete+ and +set+.
|
@@ -599,6 +635,15 @@ module Dynamoid
|
|
599
635
|
# t.add(age: 1)
|
600
636
|
# end
|
601
637
|
#
|
638
|
+
# To check if some attribute (or attributes) isn't stored in a DynamoDB
|
639
|
+
# item (e.g. it wasn't set explicitly) there is another condition -
|
640
|
+
# +unless_exists+:
|
641
|
+
#
|
642
|
+
# user = User.create(name: 'Tylor')
|
643
|
+
# user.update!(unless_exists: [:age]) do |t|
|
644
|
+
# t.set(age: 18)
|
645
|
+
# end
|
646
|
+
#
|
602
647
|
# If a document doesn't meet conditions it raises
|
603
648
|
# +Dynamoid::Errors::StaleObjectError+ exception.
|
604
649
|
#
|
@@ -644,7 +689,7 @@ module Dynamoid
|
|
644
689
|
|
645
690
|
# Update a model.
|
646
691
|
#
|
647
|
-
#
|
692
|
+
# Doesn't run validation. Runs only +update+ callbacks. Reloads all attribute values.
|
648
693
|
#
|
649
694
|
# Accepts mandatory block in order to specify operations which will modify
|
650
695
|
# attributes. Supports following operations: +add+, +delete+ and +set+.
|
@@ -695,6 +740,15 @@ module Dynamoid
|
|
695
740
|
# t.add(age: 1)
|
696
741
|
# end
|
697
742
|
#
|
743
|
+
# To check if some attribute (or attributes) isn't stored in a DynamoDB
|
744
|
+
# item (e.g. it wasn't set explicitly) there is another condition -
|
745
|
+
# +unless_exists+:
|
746
|
+
#
|
747
|
+
# user = User.create(name: 'Tylor')
|
748
|
+
# user.update(unless_exists: [:age]) do |t|
|
749
|
+
# t.set(age: 18)
|
750
|
+
# end
|
751
|
+
#
|
698
752
|
# If a document doesn't meet conditions it just returns +false+. Otherwise it returns +true+.
|
699
753
|
#
|
700
754
|
# It will increment the +lock_version+ attribute if a table has the column,
|
@@ -736,14 +790,32 @@ module Dynamoid
|
|
736
790
|
# user.increment!(:followers_count)
|
737
791
|
# user.increment!(:followers_count, 2)
|
738
792
|
#
|
739
|
-
#
|
793
|
+
# Only `attribute` is saved. The model itself is not saved. So any other
|
794
|
+
# modified attributes will still be dirty. Validations and callbacks are
|
795
|
+
# skipped.
|
796
|
+
#
|
797
|
+
# When `:touch` option is passed the timestamp columns are updating. If
|
798
|
+
# attribute names are passed, they are updated along with updated_at
|
799
|
+
# attribute:
|
800
|
+
#
|
801
|
+
# user.increment!(:followers_count, touch: true)
|
802
|
+
# user.increment!(:followers_count, touch: :viewed_at)
|
803
|
+
# user.increment!(:followers_count, touch: [:viewed_at, :accessed_at])
|
740
804
|
#
|
741
805
|
# @param attribute [Symbol] attribute name
|
742
806
|
# @param by [Numeric] value to add (optional)
|
743
|
-
# @
|
744
|
-
|
807
|
+
# @param touch [true | Symbol | Array[Symbol]] to update update_at attribute and optionally the specified ones
|
808
|
+
# @return [Dynamoid::Document] self
|
809
|
+
def increment!(attribute, by = 1, touch: nil)
|
745
810
|
increment(attribute, by)
|
746
|
-
|
811
|
+
change = read_attribute(attribute) - (attribute_was(attribute) || 0)
|
812
|
+
|
813
|
+
run_callbacks :touch do
|
814
|
+
self.class.inc(hash_key, range_value, attribute => change, touch: touch)
|
815
|
+
clear_attribute_changes(attribute)
|
816
|
+
end
|
817
|
+
|
818
|
+
self
|
747
819
|
end
|
748
820
|
|
749
821
|
# Change numeric attribute value.
|
@@ -758,9 +830,7 @@ module Dynamoid
|
|
758
830
|
# @param by [Numeric] value to subtract (optional)
|
759
831
|
# @return [Dynamoid::Document] self
|
760
832
|
def decrement(attribute, by = 1)
|
761
|
-
|
762
|
-
self[attribute] -= by
|
763
|
-
self
|
833
|
+
increment(attribute, -by)
|
764
834
|
end
|
765
835
|
|
766
836
|
# Change numeric attribute value and save a model.
|
@@ -771,14 +841,24 @@ module Dynamoid
|
|
771
841
|
# user.decrement!(:followers_count)
|
772
842
|
# user.decrement!(:followers_count, 2)
|
773
843
|
#
|
774
|
-
#
|
844
|
+
# Only `attribute` is saved. The model itself is not saved. So any other
|
845
|
+
# modified attributes will still be dirty. Validations and callbacks are
|
846
|
+
# skipped.
|
847
|
+
#
|
848
|
+
# When `:touch` option is passed the timestamp columns are updating. If
|
849
|
+
# attribute names are passed, they are updated along with updated_at
|
850
|
+
# attribute:
|
851
|
+
#
|
852
|
+
# user.decrement!(:followers_count, touch: true)
|
853
|
+
# user.decrement!(:followers_count, touch: :viewed_at)
|
854
|
+
# user.decrement!(:followers_count, touch: [:viewed_at, :accessed_at])
|
775
855
|
#
|
776
856
|
# @param attribute [Symbol] attribute name
|
777
857
|
# @param by [Numeric] value to subtract (optional)
|
778
|
-
# @
|
779
|
-
|
780
|
-
|
781
|
-
|
858
|
+
# @param touch [true | Symbol | Array[Symbol]] to update update_at attribute and optionally the specified ones
|
859
|
+
# @return [Dynamoid::Document] self
|
860
|
+
def decrement!(attribute, by = 1, touch: nil)
|
861
|
+
increment!(attribute, -by, touch: touch)
|
782
862
|
end
|
783
863
|
|
784
864
|
# Delete a model.
|
@@ -844,7 +924,7 @@ module Dynamoid
|
|
844
924
|
|
845
925
|
Dynamoid.adapter.delete(self.class.table_name, hash_key, options)
|
846
926
|
|
847
|
-
self.class.associations.
|
927
|
+
self.class.associations.each_key do |name|
|
848
928
|
send(name).disassociate_source
|
849
929
|
end
|
850
930
|
|
@@ -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,10 +280,8 @@ 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
|
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
|
data/lib/dynamoid/version.rb
CHANGED