dynamoid 3.10.0 → 3.11.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -0
- data/README.md +182 -2
- data/dynamoid.gemspec +4 -4
- data/lib/dynamoid/adapter.rb +1 -1
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/filter_expression_convertor.rb +53 -18
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/item_updater.rb +5 -4
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/projection_expression_convertor.rb +9 -7
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/query.rb +1 -1
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/scan.rb +1 -1
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/transact.rb +31 -0
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3.rb +9 -5
- data/lib/dynamoid/components.rb +1 -0
- data/lib/dynamoid/config.rb +2 -0
- data/lib/dynamoid/criteria/chain.rb +63 -18
- data/lib/dynamoid/criteria/where_conditions.rb +13 -6
- data/lib/dynamoid/dirty.rb +86 -11
- data/lib/dynamoid/dumping.rb +36 -14
- data/lib/dynamoid/errors.rb +14 -2
- data/lib/dynamoid/finders.rb +6 -6
- data/lib/dynamoid/loadable.rb +1 -0
- data/lib/dynamoid/persistence/inc.rb +6 -7
- data/lib/dynamoid/persistence/item_updater_with_casting_and_dumping.rb +36 -0
- data/lib/dynamoid/persistence/item_updater_with_dumping.rb +33 -0
- data/lib/dynamoid/persistence/save.rb +5 -2
- data/lib/dynamoid/persistence/update_fields.rb +5 -3
- data/lib/dynamoid/persistence/upsert.rb +5 -4
- data/lib/dynamoid/persistence.rb +38 -17
- data/lib/dynamoid/transaction_write/base.rb +47 -0
- data/lib/dynamoid/transaction_write/create.rb +49 -0
- data/lib/dynamoid/transaction_write/delete_with_instance.rb +60 -0
- data/lib/dynamoid/transaction_write/delete_with_primary_key.rb +59 -0
- data/lib/dynamoid/transaction_write/destroy.rb +79 -0
- data/lib/dynamoid/transaction_write/save.rb +164 -0
- data/lib/dynamoid/transaction_write/update_attributes.rb +46 -0
- data/lib/dynamoid/transaction_write/update_fields.rb +102 -0
- data/lib/dynamoid/transaction_write/upsert.rb +96 -0
- data/lib/dynamoid/transaction_write.rb +464 -0
- data/lib/dynamoid/type_casting.rb +3 -1
- data/lib/dynamoid/undumping.rb +13 -2
- data/lib/dynamoid/validations.rb +1 -1
- data/lib/dynamoid/version.rb +1 -1
- data/lib/dynamoid.rb +7 -0
- metadata +18 -5
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'item_updater_with_casting_and_dumping'
|
4
|
+
|
3
5
|
module Dynamoid
|
4
6
|
module Persistence
|
5
7
|
# @private
|
@@ -32,11 +34,10 @@ module Dynamoid
|
|
32
34
|
|
33
35
|
def update_item
|
34
36
|
Dynamoid.adapter.update_item(@model_class.table_name, @partition_key, options_to_update_item) do |t|
|
35
|
-
|
36
|
-
value_casted = TypeCasting.cast_field(v, @model_class.attributes[k])
|
37
|
-
value_dumped = Dumping.dump_field(value_casted, @model_class.attributes[k])
|
37
|
+
item_updater = ItemUpdaterWithCastingAndDumping.new(@model_class, t)
|
38
38
|
|
39
|
-
|
39
|
+
@attributes.each do |k, v|
|
40
|
+
item_updater.set(k => v)
|
40
41
|
end
|
41
42
|
end
|
42
43
|
end
|
data/lib/dynamoid/persistence.rb
CHANGED
@@ -10,6 +10,7 @@ require 'dynamoid/persistence/upsert'
|
|
10
10
|
require 'dynamoid/persistence/save'
|
11
11
|
require 'dynamoid/persistence/inc'
|
12
12
|
require 'dynamoid/persistence/update_validations'
|
13
|
+
require 'dynamoid/persistence/item_updater_with_dumping'
|
13
14
|
|
14
15
|
# encoding: utf-8
|
15
16
|
module Dynamoid
|
@@ -18,8 +19,9 @@ module Dynamoid
|
|
18
19
|
module Persistence
|
19
20
|
extend ActiveSupport::Concern
|
20
21
|
|
21
|
-
attr_accessor :new_record
|
22
|
+
attr_accessor :new_record, :destroyed
|
22
23
|
alias new_record? new_record
|
24
|
+
alias destroyed? destroyed
|
23
25
|
|
24
26
|
# @private
|
25
27
|
UNIX_EPOCH_DATE = Date.new(1970, 1, 1).freeze
|
@@ -166,7 +168,7 @@ module Dynamoid
|
|
166
168
|
|
167
169
|
# Create a model.
|
168
170
|
#
|
169
|
-
# Initializes a new model and immediately saves it
|
171
|
+
# Initializes a new model and immediately saves it into DynamoDB.
|
170
172
|
#
|
171
173
|
# User.create(first_name: 'Mark', last_name: 'Tyler')
|
172
174
|
#
|
@@ -174,7 +176,8 @@ module Dynamoid
|
|
174
176
|
#
|
175
177
|
# User.create([{ first_name: 'Alice' }, { first_name: 'Bob' }])
|
176
178
|
#
|
177
|
-
#
|
179
|
+
# Instantiates a model and pass it into an optional block to set other
|
180
|
+
# attributes.
|
178
181
|
#
|
179
182
|
# User.create(first_name: 'Mark') do |u|
|
180
183
|
# u.age = 21
|
@@ -182,7 +185,7 @@ module Dynamoid
|
|
182
185
|
#
|
183
186
|
# Validates model and runs callbacks.
|
184
187
|
#
|
185
|
-
# @param attrs [Hash|Array
|
188
|
+
# @param attrs [Hash|Array<Hash>] Attributes of a model
|
186
189
|
# @param block [Proc] Block to process a document after initialization
|
187
190
|
# @return [Dynamoid::Document] The created document
|
188
191
|
# @since 0.2.0
|
@@ -196,12 +199,28 @@ module Dynamoid
|
|
196
199
|
|
197
200
|
# Create a model.
|
198
201
|
#
|
199
|
-
# Initializes a new object and immediately saves it
|
202
|
+
# Initializes a new object and immediately saves it into DynamoDB.
|
203
|
+
#
|
204
|
+
# User.create!(first_name: 'Mark', last_name: 'Tyler')
|
205
|
+
#
|
200
206
|
# Raises an exception +Dynamoid::Errors::DocumentNotValid+ if validation
|
201
|
-
# failed.
|
207
|
+
# failed.
|
208
|
+
#
|
209
|
+
# Accepts both Hash and Array of Hashes and can create several
|
202
210
|
# models.
|
203
211
|
#
|
204
|
-
#
|
212
|
+
# User.create!([{ first_name: 'Alice' }, { first_name: 'Bob' }])
|
213
|
+
#
|
214
|
+
# Instantiates a model and pass it into an optional block to set other
|
215
|
+
# attributes.
|
216
|
+
#
|
217
|
+
# User.create!(first_name: 'Mark') do |u|
|
218
|
+
# u.age = 21
|
219
|
+
# end
|
220
|
+
#
|
221
|
+
# Validates model and runs callbacks.
|
222
|
+
#
|
223
|
+
# @param attrs [Hash|Array<Hash>] Attributes with which to create the object.
|
205
224
|
# @param block [Proc] Block to process a document after initialization
|
206
225
|
# @return [Dynamoid::Document] The created document
|
207
226
|
# @since 0.2.0
|
@@ -348,7 +367,7 @@ module Dynamoid
|
|
348
367
|
# an updated document back with one HTTP request.
|
349
368
|
#
|
350
369
|
# Raises a +Dynamoid::Errors::UnknownAttribute+ exception if any of the
|
351
|
-
# attributes is not
|
370
|
+
# attributes is not declared in the model class.
|
352
371
|
#
|
353
372
|
# @param hash_key_value [Scalar value] hash key
|
354
373
|
# @param range_key_value [Scalar value] range key (optional)
|
@@ -404,7 +423,7 @@ module Dynamoid
|
|
404
423
|
# @param hash_key_value [Scalar value] hash key
|
405
424
|
# @param range_key_value [Scalar value] range key (optional)
|
406
425
|
# @param counters [Hash] value to increase by
|
407
|
-
# @option counters [true | Symbol | Array
|
426
|
+
# @option counters [true | Symbol | Array<Symbol>] :touch to update update_at attribute and optionally the specified ones
|
408
427
|
# @return [Model class] self
|
409
428
|
def inc(hash_key_value, range_key_value = nil, counters)
|
410
429
|
Inc.call(self, hash_key_value, range_key_value, counters)
|
@@ -490,7 +509,7 @@ module Dynamoid
|
|
490
509
|
#
|
491
510
|
# +save+ by default sets timestamps attributes - +created_at+ and
|
492
511
|
# +updated_at+ when creates new model and updates +updated_at+ attribute
|
493
|
-
# when
|
512
|
+
# when updates already existing one.
|
494
513
|
#
|
495
514
|
# Changing +updated_at+ attribute at updating a model can be skipped with
|
496
515
|
# +touch: false+ option:
|
@@ -535,7 +554,9 @@ module Dynamoid
|
|
535
554
|
end
|
536
555
|
|
537
556
|
# Update multiple attributes at once, saving the object once the updates
|
538
|
-
# are complete.
|
557
|
+
# are complete.
|
558
|
+
#
|
559
|
+
# Returns +true+ if saving is successful and +false+
|
539
560
|
# otherwise.
|
540
561
|
#
|
541
562
|
# user.update_attributes(age: 27, last_name: 'Tylor')
|
@@ -668,12 +689,12 @@ module Dynamoid
|
|
668
689
|
update_item_options = options.merge(conditions: conditions)
|
669
690
|
|
670
691
|
new_attrs = Dynamoid.adapter.update_item(table_name, hash_key, update_item_options) do |t|
|
671
|
-
|
692
|
+
item_updater = ItemUpdaterWithDumping.new(self.class, t)
|
693
|
+
|
694
|
+
item_updater.add(lock_version: 1) if self.class.attributes[:lock_version]
|
672
695
|
|
673
696
|
if self.class.timestamps_enabled?
|
674
|
-
|
675
|
-
time_now_dumped = Dumping.dump_field(time_now, self.class.attributes[:updated_at])
|
676
|
-
t.set(updated_at: time_now_dumped)
|
697
|
+
item_updater.set(updated_at: DateTime.now.in_time_zone(Time.zone))
|
677
698
|
end
|
678
699
|
|
679
700
|
yield t
|
@@ -804,7 +825,7 @@ module Dynamoid
|
|
804
825
|
#
|
805
826
|
# @param attribute [Symbol] attribute name
|
806
827
|
# @param by [Numeric] value to add (optional)
|
807
|
-
# @param touch [true | Symbol | Array
|
828
|
+
# @param touch [true | Symbol | Array<Symbol>] to update update_at attribute and optionally the specified ones
|
808
829
|
# @return [Dynamoid::Document] self
|
809
830
|
def increment!(attribute, by = 1, touch: nil)
|
810
831
|
increment(attribute, by)
|
@@ -855,7 +876,7 @@ module Dynamoid
|
|
855
876
|
#
|
856
877
|
# @param attribute [Symbol] attribute name
|
857
878
|
# @param by [Numeric] value to subtract (optional)
|
858
|
-
# @param touch [true | Symbol | Array
|
879
|
+
# @param touch [true | Symbol | Array<Symbol>] to update update_at attribute and optionally the specified ones
|
859
880
|
# @return [Dynamoid::Document] self
|
860
881
|
def decrement!(attribute, by = 1, touch: nil)
|
861
882
|
increment!(attribute, -by, touch: touch)
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dynamoid
|
4
|
+
class TransactionWrite
|
5
|
+
class Base
|
6
|
+
# Callback called at "initialization" or "registration" an action
|
7
|
+
# before changes are persisted. It's a proper place to validate
|
8
|
+
# a model or run callbacks
|
9
|
+
def on_registration
|
10
|
+
raise 'Not implemented'
|
11
|
+
end
|
12
|
+
|
13
|
+
# Callback called after changes are persisted.
|
14
|
+
# It's a proper place to mark changes in a model as applied.
|
15
|
+
def on_commit
|
16
|
+
raise 'Not implemented'
|
17
|
+
end
|
18
|
+
|
19
|
+
# Callback called when a transaction is rolled back.
|
20
|
+
# It's a proper place to undo changes made in after_... callbacks.
|
21
|
+
def on_rollback
|
22
|
+
raise 'Not implemented'
|
23
|
+
end
|
24
|
+
|
25
|
+
# Whether some callback aborted or canceled an action
|
26
|
+
def aborted?
|
27
|
+
raise 'Not implemented'
|
28
|
+
end
|
29
|
+
|
30
|
+
# Whether there are changes to persist, e.g. updating a model with no
|
31
|
+
# attribute changed is skipped.
|
32
|
+
def skipped?
|
33
|
+
raise 'Not implemented'
|
34
|
+
end
|
35
|
+
|
36
|
+
# Value returned to a user as an action result
|
37
|
+
def observable_by_user_result
|
38
|
+
raise 'Not implemented'
|
39
|
+
end
|
40
|
+
|
41
|
+
# Coresponding part of a final request body
|
42
|
+
def action_request
|
43
|
+
raise 'Not implemented'
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
|
5
|
+
module Dynamoid
|
6
|
+
class TransactionWrite
|
7
|
+
class Create < Base
|
8
|
+
def initialize(model_class, attributes = {}, **options, &block)
|
9
|
+
super()
|
10
|
+
|
11
|
+
@model = model_class.new(attributes)
|
12
|
+
|
13
|
+
if block
|
14
|
+
yield(@model)
|
15
|
+
end
|
16
|
+
|
17
|
+
@save_action = Save.new(@model, **options)
|
18
|
+
end
|
19
|
+
|
20
|
+
def on_registration
|
21
|
+
@save_action.on_registration
|
22
|
+
end
|
23
|
+
|
24
|
+
def on_commit
|
25
|
+
@save_action.on_commit
|
26
|
+
end
|
27
|
+
|
28
|
+
def on_rollback
|
29
|
+
@save_action.on_rollback
|
30
|
+
end
|
31
|
+
|
32
|
+
def aborted?
|
33
|
+
@save_action.aborted?
|
34
|
+
end
|
35
|
+
|
36
|
+
def skipped?
|
37
|
+
false
|
38
|
+
end
|
39
|
+
|
40
|
+
def observable_by_user_result
|
41
|
+
@model
|
42
|
+
end
|
43
|
+
|
44
|
+
def action_request
|
45
|
+
@save_action.action_request
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
|
5
|
+
module Dynamoid
|
6
|
+
class TransactionWrite
|
7
|
+
class DeleteWithInstance < Base
|
8
|
+
def initialize(model)
|
9
|
+
super()
|
10
|
+
|
11
|
+
@model = model
|
12
|
+
@model_class = model.class
|
13
|
+
end
|
14
|
+
|
15
|
+
def on_registration
|
16
|
+
validate_model!
|
17
|
+
end
|
18
|
+
|
19
|
+
def on_commit
|
20
|
+
@model.destroyed = true
|
21
|
+
end
|
22
|
+
|
23
|
+
def on_rollback; end
|
24
|
+
|
25
|
+
def aborted?
|
26
|
+
false
|
27
|
+
end
|
28
|
+
|
29
|
+
def skipped?
|
30
|
+
false
|
31
|
+
end
|
32
|
+
|
33
|
+
def observable_by_user_result
|
34
|
+
@model
|
35
|
+
end
|
36
|
+
|
37
|
+
def action_request
|
38
|
+
key = { @model_class.hash_key => @model.hash_key }
|
39
|
+
|
40
|
+
if @model_class.range_key?
|
41
|
+
key[@model_class.range_key] = @model.range_value
|
42
|
+
end
|
43
|
+
|
44
|
+
{
|
45
|
+
delete: {
|
46
|
+
key: key,
|
47
|
+
table_name: @model_class.table_name
|
48
|
+
}
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def validate_model!
|
55
|
+
raise Dynamoid::Errors::MissingHashKey if @model.hash_key.nil?
|
56
|
+
raise Dynamoid::Errors::MissingRangeKey if @model_class.range_key? && @model.range_value.nil?
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
|
5
|
+
module Dynamoid
|
6
|
+
class TransactionWrite
|
7
|
+
class DeleteWithPrimaryKey < Base
|
8
|
+
def initialize(model_class, hash_key, range_key)
|
9
|
+
super()
|
10
|
+
|
11
|
+
@model_class = model_class
|
12
|
+
@hash_key = hash_key
|
13
|
+
@range_key = range_key
|
14
|
+
end
|
15
|
+
|
16
|
+
def on_registration
|
17
|
+
validate_primary_key!
|
18
|
+
end
|
19
|
+
|
20
|
+
def on_commit; end
|
21
|
+
|
22
|
+
def on_rollback; end
|
23
|
+
|
24
|
+
def aborted?
|
25
|
+
false
|
26
|
+
end
|
27
|
+
|
28
|
+
def skipped?
|
29
|
+
false
|
30
|
+
end
|
31
|
+
|
32
|
+
def observable_by_user_result
|
33
|
+
nil
|
34
|
+
end
|
35
|
+
|
36
|
+
def action_request
|
37
|
+
key = { @model_class.hash_key => @hash_key }
|
38
|
+
|
39
|
+
if @model_class.range_key?
|
40
|
+
key[@model_class.range_key] = @range_key
|
41
|
+
end
|
42
|
+
|
43
|
+
{
|
44
|
+
delete: {
|
45
|
+
key: key,
|
46
|
+
table_name: @model_class.table_name
|
47
|
+
}
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def validate_primary_key!
|
54
|
+
raise Dynamoid::Errors::MissingHashKey if @hash_key.nil?
|
55
|
+
raise Dynamoid::Errors::MissingRangeKey if @model_class.range_key? && @range_key.nil?
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
|
5
|
+
module Dynamoid
|
6
|
+
class TransactionWrite
|
7
|
+
class Destroy < Base
|
8
|
+
def initialize(model, **options)
|
9
|
+
super()
|
10
|
+
|
11
|
+
@model = model
|
12
|
+
@options = options
|
13
|
+
@model_class = model.class
|
14
|
+
@aborted = false
|
15
|
+
end
|
16
|
+
|
17
|
+
def on_registration
|
18
|
+
validate_model!
|
19
|
+
|
20
|
+
@aborted = true
|
21
|
+
@model.run_callbacks(:destroy) do
|
22
|
+
@aborted = false
|
23
|
+
true
|
24
|
+
end
|
25
|
+
|
26
|
+
if @aborted && @options[:raise_error]
|
27
|
+
raise Dynamoid::Errors::RecordNotDestroyed, @model
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def on_commit
|
32
|
+
return if @aborted
|
33
|
+
|
34
|
+
@model.destroyed = true
|
35
|
+
@model.run_callbacks(:commit)
|
36
|
+
end
|
37
|
+
|
38
|
+
def on_rollback
|
39
|
+
@model.run_callbacks(:rollback)
|
40
|
+
end
|
41
|
+
|
42
|
+
def aborted?
|
43
|
+
@aborted
|
44
|
+
end
|
45
|
+
|
46
|
+
def skipped?
|
47
|
+
false
|
48
|
+
end
|
49
|
+
|
50
|
+
def observable_by_user_result
|
51
|
+
return false if @aborted
|
52
|
+
|
53
|
+
@model
|
54
|
+
end
|
55
|
+
|
56
|
+
def action_request
|
57
|
+
key = { @model_class.hash_key => @model.hash_key }
|
58
|
+
|
59
|
+
if @model_class.range_key?
|
60
|
+
key[@model_class.range_key] = @model.range_value
|
61
|
+
end
|
62
|
+
|
63
|
+
{
|
64
|
+
delete: {
|
65
|
+
key: key,
|
66
|
+
table_name: @model_class.table_name
|
67
|
+
}
|
68
|
+
}
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def validate_model!
|
74
|
+
raise Dynamoid::Errors::MissingHashKey if @model.hash_key.nil?
|
75
|
+
raise Dynamoid::Errors::MissingRangeKey if @model_class.range_key? && @model.range_value.nil?
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
|
5
|
+
module Dynamoid
|
6
|
+
class TransactionWrite
|
7
|
+
class Save < Base
|
8
|
+
def initialize(model, **options)
|
9
|
+
super()
|
10
|
+
|
11
|
+
@model = model
|
12
|
+
@model_class = model.class
|
13
|
+
@options = options
|
14
|
+
|
15
|
+
@aborted = false
|
16
|
+
@was_new_record = model.new_record?
|
17
|
+
@valid = nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def on_registration
|
21
|
+
validate_model!
|
22
|
+
|
23
|
+
if @options[:validate] != false && !(@valid = @model.valid?)
|
24
|
+
if @options[:raise_error]
|
25
|
+
raise Dynamoid::Errors::DocumentNotValid, @model
|
26
|
+
else
|
27
|
+
@aborted = true
|
28
|
+
return
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
@aborted = true
|
33
|
+
callback_name = @was_new_record ? :create : :update
|
34
|
+
|
35
|
+
@model.run_callbacks(:save) do
|
36
|
+
@model.run_callbacks(callback_name) do
|
37
|
+
@model.run_callbacks(:validate) do
|
38
|
+
@aborted = false
|
39
|
+
true
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
if @aborted && @options[:raise_error]
|
45
|
+
raise Dynamoid::Errors::RecordNotSaved, @model
|
46
|
+
end
|
47
|
+
|
48
|
+
if @was_new_record && @model.hash_key.nil?
|
49
|
+
@model.hash_key = SecureRandom.uuid
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def on_commit
|
54
|
+
return if @aborted
|
55
|
+
|
56
|
+
@model.changes_applied
|
57
|
+
|
58
|
+
if @was_new_record
|
59
|
+
@model.new_record = false
|
60
|
+
end
|
61
|
+
|
62
|
+
@model.run_callbacks(:commit)
|
63
|
+
end
|
64
|
+
|
65
|
+
def on_rollback
|
66
|
+
@model.run_callbacks(:rollback)
|
67
|
+
end
|
68
|
+
|
69
|
+
def aborted?
|
70
|
+
@aborted
|
71
|
+
end
|
72
|
+
|
73
|
+
def skipped?
|
74
|
+
@model.persisted? && !@model.changed?
|
75
|
+
end
|
76
|
+
|
77
|
+
def observable_by_user_result
|
78
|
+
!@aborted
|
79
|
+
end
|
80
|
+
|
81
|
+
def action_request
|
82
|
+
if @was_new_record
|
83
|
+
action_request_to_create
|
84
|
+
else
|
85
|
+
action_request_to_update
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
def validate_model!
|
92
|
+
raise Dynamoid::Errors::MissingHashKey if !@was_new_record && @model.hash_key.nil?
|
93
|
+
raise Dynamoid::Errors::MissingRangeKey if @model_class.range_key? && @model.range_value.nil?
|
94
|
+
end
|
95
|
+
|
96
|
+
def action_request_to_create
|
97
|
+
touch_model_timestamps(skip_created_at: false)
|
98
|
+
|
99
|
+
attributes_dumped = Dynamoid::Dumping.dump_attributes(@model.attributes, @model_class.attributes)
|
100
|
+
|
101
|
+
# require primary key not to exist yet
|
102
|
+
condition = "attribute_not_exists(#{@model_class.hash_key})"
|
103
|
+
if @model_class.range_key?
|
104
|
+
condition += " AND attribute_not_exists(#{@model_class.range_key})"
|
105
|
+
end
|
106
|
+
|
107
|
+
{
|
108
|
+
put: {
|
109
|
+
item: attributes_dumped,
|
110
|
+
table_name: @model_class.table_name,
|
111
|
+
condition_expression: condition
|
112
|
+
}
|
113
|
+
}
|
114
|
+
end
|
115
|
+
|
116
|
+
def action_request_to_update
|
117
|
+
touch_model_timestamps(skip_created_at: true)
|
118
|
+
|
119
|
+
# changed attributes to persist
|
120
|
+
changes = @model.attributes.slice(*@model.changed.map(&:to_sym))
|
121
|
+
changes_dumped = Dynamoid::Dumping.dump_attributes(changes, @model_class.attributes)
|
122
|
+
|
123
|
+
# primary key to look up an item to update
|
124
|
+
key = { @model_class.hash_key => @model.hash_key }
|
125
|
+
key[@model_class.range_key] = @model.range_value if @model_class.range_key?
|
126
|
+
|
127
|
+
# Build UpdateExpression and keep names and values placeholders mapping
|
128
|
+
# in ExpressionAttributeNames and ExpressionAttributeValues.
|
129
|
+
update_expression_statements = []
|
130
|
+
expression_attribute_names = {}
|
131
|
+
expression_attribute_values = {}
|
132
|
+
|
133
|
+
changes_dumped.each_with_index do |(name, value), i|
|
134
|
+
name_placeholder = "#_n#{i}"
|
135
|
+
value_placeholder = ":_s#{i}"
|
136
|
+
|
137
|
+
update_expression_statements << "#{name_placeholder} = #{value_placeholder}"
|
138
|
+
expression_attribute_names[name_placeholder] = name
|
139
|
+
expression_attribute_values[value_placeholder] = value
|
140
|
+
end
|
141
|
+
|
142
|
+
update_expression = "SET #{update_expression_statements.join(', ')}"
|
143
|
+
|
144
|
+
{
|
145
|
+
update: {
|
146
|
+
key: key,
|
147
|
+
table_name: @model_class.table_name,
|
148
|
+
update_expression: update_expression,
|
149
|
+
expression_attribute_names: expression_attribute_names,
|
150
|
+
expression_attribute_values: expression_attribute_values
|
151
|
+
}
|
152
|
+
}
|
153
|
+
end
|
154
|
+
|
155
|
+
def touch_model_timestamps(skip_created_at:)
|
156
|
+
return unless @model_class.timestamps_enabled?
|
157
|
+
|
158
|
+
timestamp = DateTime.now.in_time_zone(Time.zone)
|
159
|
+
@model.updated_at = timestamp unless @options[:touch] == false && !@was_new_record
|
160
|
+
@model.created_at ||= timestamp unless skip_created_at
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
require 'dynamoid/persistence/update_validations'
|
5
|
+
|
6
|
+
module Dynamoid
|
7
|
+
class TransactionWrite
|
8
|
+
class UpdateAttributes < Base
|
9
|
+
def initialize(model, attributes, **options)
|
10
|
+
super()
|
11
|
+
|
12
|
+
@model = model
|
13
|
+
@model.assign_attributes(attributes)
|
14
|
+
@save_action = Save.new(model, **options)
|
15
|
+
end
|
16
|
+
|
17
|
+
def on_registration
|
18
|
+
@save_action.on_registration
|
19
|
+
end
|
20
|
+
|
21
|
+
def on_commit
|
22
|
+
@save_action.on_commit
|
23
|
+
end
|
24
|
+
|
25
|
+
def on_rollback
|
26
|
+
@save_action.on_rollback
|
27
|
+
end
|
28
|
+
|
29
|
+
def aborted?
|
30
|
+
@save_action.aborted?
|
31
|
+
end
|
32
|
+
|
33
|
+
def skipped?
|
34
|
+
@save_action.skipped?
|
35
|
+
end
|
36
|
+
|
37
|
+
def observable_by_user_result
|
38
|
+
@save_action.observable_by_user_result
|
39
|
+
end
|
40
|
+
|
41
|
+
def action_request
|
42
|
+
@save_action.action_request
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|