dynamoid 3.13.1 → 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 +34 -5
- 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 +3 -2
- 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,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dynamoid
|
|
4
|
+
module Transactions
|
|
5
|
+
class Mutation
|
|
6
|
+
# @private
|
|
7
|
+
module Builders
|
|
8
|
+
class DeleteRequestBuilder
|
|
9
|
+
attr_writer :hash_key, :range_key, :condition_expression
|
|
10
|
+
|
|
11
|
+
def initialize(model_class)
|
|
12
|
+
@model_class = model_class
|
|
13
|
+
@condition_expression = nil
|
|
14
|
+
@extra_attribute_names = {}
|
|
15
|
+
@extra_attribute_values = {}
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def add_expression_attribute_name(placeholder, name)
|
|
19
|
+
@extra_attribute_names[placeholder] = name
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def add_expression_attribute_value(placeholder, value)
|
|
23
|
+
@extra_attribute_values[placeholder] = value
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def request
|
|
27
|
+
key = { @model_class.hash_key => @hash_key }
|
|
28
|
+
key[@model_class.range_key] = @range_key if @model_class.range_key?
|
|
29
|
+
|
|
30
|
+
{
|
|
31
|
+
delete: {
|
|
32
|
+
key: key,
|
|
33
|
+
table_name: @model_class.table_name,
|
|
34
|
+
expression_attribute_names: @extra_attribute_names.presence,
|
|
35
|
+
expression_attribute_values: @extra_attribute_values.presence,
|
|
36
|
+
condition_expression: @condition_expression
|
|
37
|
+
}.compact
|
|
38
|
+
}
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dynamoid
|
|
4
|
+
module Transactions
|
|
5
|
+
class Mutation
|
|
6
|
+
# @private
|
|
7
|
+
module Builders
|
|
8
|
+
class UpdateRequestBuilder
|
|
9
|
+
attr_writer :hash_key, :range_key, :condition_expression
|
|
10
|
+
|
|
11
|
+
def initialize(model_class)
|
|
12
|
+
@model_class = model_class
|
|
13
|
+
|
|
14
|
+
@attributes_to_set = {}
|
|
15
|
+
@attributes_to_add = {}
|
|
16
|
+
@attributes_to_delete = {}
|
|
17
|
+
@attributes_to_remove = []
|
|
18
|
+
@condition_expression = nil
|
|
19
|
+
@extra_attribute_names = {}
|
|
20
|
+
@extra_attribute_values = {}
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def add_expression_attribute_name(placeholder, name)
|
|
24
|
+
@extra_attribute_names[placeholder] = name
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def add_expression_attribute_value(placeholder, value)
|
|
28
|
+
@extra_attribute_values[placeholder] = value
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def set_attributes(attributes) # rubocop:disable Naming/AccessorMethodName
|
|
32
|
+
@attributes_to_set.merge!(attributes)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def add_value(name, value)
|
|
36
|
+
@attributes_to_add[name] = value
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def delete_value(name, value)
|
|
40
|
+
@attributes_to_delete[name] = value
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def remove_attributes(names)
|
|
44
|
+
@attributes_to_remove.concat(names)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def request
|
|
48
|
+
key = { @model_class.hash_key => @hash_key }
|
|
49
|
+
key[@model_class.range_key] = @range_key if @model_class.range_key?
|
|
50
|
+
|
|
51
|
+
# Build UpdateExpression and keep names and values placeholders mapping
|
|
52
|
+
# in ExpressionAttributeNames and ExpressionAttributeValues.
|
|
53
|
+
update_expression_statements = []
|
|
54
|
+
expression_attribute_names = @extra_attribute_names.dup
|
|
55
|
+
expression_attribute_values = @extra_attribute_values.dup
|
|
56
|
+
name_placeholder = '#_n0'
|
|
57
|
+
value_placeholder = ':_v0'
|
|
58
|
+
|
|
59
|
+
unless @attributes_to_set.empty?
|
|
60
|
+
statements = []
|
|
61
|
+
|
|
62
|
+
@attributes_to_set.each do |name, value|
|
|
63
|
+
statements << "#{name_placeholder} = #{value_placeholder}"
|
|
64
|
+
|
|
65
|
+
expression_attribute_names[name_placeholder] = name
|
|
66
|
+
expression_attribute_values[value_placeholder] = value
|
|
67
|
+
|
|
68
|
+
name_placeholder = name_placeholder.succ
|
|
69
|
+
value_placeholder = value_placeholder.succ
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
update_expression_statements << "SET #{statements.join(', ')}"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
unless @attributes_to_add.empty?
|
|
76
|
+
statements = []
|
|
77
|
+
|
|
78
|
+
@attributes_to_add.each do |name, value|
|
|
79
|
+
statements << "#{name_placeholder} #{value_placeholder}"
|
|
80
|
+
|
|
81
|
+
expression_attribute_names[name_placeholder] = name
|
|
82
|
+
expression_attribute_values[value_placeholder] = value
|
|
83
|
+
|
|
84
|
+
name_placeholder = name_placeholder.succ
|
|
85
|
+
value_placeholder = value_placeholder.succ
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
update_expression_statements << "ADD #{statements.join(', ')}"
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
unless @attributes_to_delete.empty?
|
|
92
|
+
statements = []
|
|
93
|
+
|
|
94
|
+
@attributes_to_delete.each do |name, value|
|
|
95
|
+
statements << "#{name_placeholder} #{value_placeholder}"
|
|
96
|
+
|
|
97
|
+
expression_attribute_names[name_placeholder] = name
|
|
98
|
+
expression_attribute_values[value_placeholder] = value
|
|
99
|
+
|
|
100
|
+
name_placeholder = name_placeholder.succ
|
|
101
|
+
value_placeholder = value_placeholder.succ
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
update_expression_statements << "DELETE #{statements.join(', ')}"
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
unless @attributes_to_remove.empty?
|
|
108
|
+
name_placeholders = []
|
|
109
|
+
|
|
110
|
+
@attributes_to_remove.each do |name|
|
|
111
|
+
name_placeholders << name_placeholder
|
|
112
|
+
|
|
113
|
+
expression_attribute_names[name_placeholder] = name
|
|
114
|
+
|
|
115
|
+
name_placeholder = name_placeholder.succ
|
|
116
|
+
value_placeholder = value_placeholder.succ
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
update_expression_statements << "REMOVE #{name_placeholders.join(', ')}"
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
update_expression = update_expression_statements.join(' ')
|
|
123
|
+
|
|
124
|
+
{
|
|
125
|
+
update: {
|
|
126
|
+
key: key,
|
|
127
|
+
table_name: @model_class.table_name,
|
|
128
|
+
update_expression: update_expression,
|
|
129
|
+
expression_attribute_names: expression_attribute_names,
|
|
130
|
+
expression_attribute_values: expression_attribute_values,
|
|
131
|
+
condition_expression: @condition_expression
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base'
|
|
4
|
+
|
|
5
|
+
module Dynamoid
|
|
6
|
+
module Transactions
|
|
7
|
+
class Mutation
|
|
8
|
+
# @private
|
|
9
|
+
class Create < Base
|
|
10
|
+
def initialize(model_class, attributes = {}, **options, &block)
|
|
11
|
+
super()
|
|
12
|
+
|
|
13
|
+
@model = model_class.new(attributes)
|
|
14
|
+
|
|
15
|
+
if block
|
|
16
|
+
yield(@model)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
@save_action = Save.new(@model, **options)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def on_registration
|
|
23
|
+
@save_action.on_registration
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def on_commit
|
|
27
|
+
@save_action.on_commit
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def on_rollback
|
|
31
|
+
@save_action.on_rollback
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def aborted?
|
|
35
|
+
@save_action.aborted?
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def skipped?
|
|
39
|
+
false
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def observable_by_user_result
|
|
43
|
+
@model
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def action_requests
|
|
47
|
+
@save_action.action_requests
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base'
|
|
4
|
+
require_relative 'builders/delete_request_builder'
|
|
5
|
+
|
|
6
|
+
module Dynamoid
|
|
7
|
+
module Transactions
|
|
8
|
+
class Mutation
|
|
9
|
+
# @private
|
|
10
|
+
class DeleteWithInstance < Base
|
|
11
|
+
def initialize(model)
|
|
12
|
+
super()
|
|
13
|
+
|
|
14
|
+
@model = model
|
|
15
|
+
@model_class = model.class
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def on_registration
|
|
19
|
+
validate_model!
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def on_commit
|
|
23
|
+
@model.destroyed = true
|
|
24
|
+
@model.run_callbacks(:commit)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def on_rollback
|
|
28
|
+
@model.run_callbacks(:rollback)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def aborted?
|
|
32
|
+
false
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def skipped?
|
|
36
|
+
false
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def observable_by_user_result
|
|
40
|
+
@model
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def action_requests
|
|
44
|
+
builder = Builders::DeleteRequestBuilder.new(@model_class)
|
|
45
|
+
builder.hash_key = dump_attribute(@model_class.hash_key, @model.hash_key)
|
|
46
|
+
builder.range_key = dump_attribute(@model_class.range_key, @model.range_value) if @model_class.range_key?
|
|
47
|
+
|
|
48
|
+
if @model_class.attributes[:lock_version]
|
|
49
|
+
lock_version = if @model.changes[:lock_version].nil?
|
|
50
|
+
@model.lock_version
|
|
51
|
+
else
|
|
52
|
+
@model.changes[:lock_version][0]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# skip concurrency control when lock_version is nil
|
|
56
|
+
if lock_version
|
|
57
|
+
builder.add_expression_attribute_name('#_lock_version', 'lock_version')
|
|
58
|
+
builder.add_expression_attribute_value(':lock_version_value', lock_version)
|
|
59
|
+
builder.condition_expression = '#_lock_version = :lock_version_value'
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
[builder.request]
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
def validate_model!
|
|
69
|
+
raise Dynamoid::Errors::MissingHashKey if @model.hash_key.nil?
|
|
70
|
+
raise Dynamoid::Errors::MissingRangeKey if @model_class.range_key? && @model.range_value.nil?
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def dump_attribute(name, value)
|
|
74
|
+
options = @model_class.attributes[name]
|
|
75
|
+
Dumping.dump_field(value, options)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base'
|
|
4
|
+
require_relative 'builders/delete_request_builder'
|
|
5
|
+
|
|
6
|
+
module Dynamoid
|
|
7
|
+
module Transactions
|
|
8
|
+
class Mutation
|
|
9
|
+
# @private
|
|
10
|
+
class DeleteWithPrimaryKey < Base
|
|
11
|
+
def initialize(model_class, hash_key, range_key)
|
|
12
|
+
super()
|
|
13
|
+
|
|
14
|
+
@model_class = model_class
|
|
15
|
+
@hash_key = hash_key
|
|
16
|
+
@range_key = range_key
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def on_registration
|
|
20
|
+
validate_primary_key!
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def on_commit; end
|
|
24
|
+
|
|
25
|
+
def on_rollback; end
|
|
26
|
+
|
|
27
|
+
def aborted?
|
|
28
|
+
false
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def skipped?
|
|
32
|
+
false
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def observable_by_user_result
|
|
36
|
+
nil
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def action_requests
|
|
40
|
+
builder = Builders::DeleteRequestBuilder.new(@model_class)
|
|
41
|
+
builder.hash_key = cast_and_dump(@model_class.hash_key, @hash_key)
|
|
42
|
+
builder.range_key = cast_and_dump(@model_class.range_key, @range_key) if @model_class.range_key?
|
|
43
|
+
|
|
44
|
+
[builder.request]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def validate_primary_key!
|
|
50
|
+
raise Dynamoid::Errors::MissingHashKey if @hash_key.nil?
|
|
51
|
+
raise Dynamoid::Errors::MissingRangeKey if @model_class.range_key? && @range_key.nil?
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def cast_and_dump(name, value)
|
|
55
|
+
options = @model_class.attributes[name]
|
|
56
|
+
value_casted = TypeCasting.cast_field(value, options)
|
|
57
|
+
Dumping.dump_field(value_casted, options)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base'
|
|
4
|
+
require_relative 'builders/delete_request_builder'
|
|
5
|
+
|
|
6
|
+
module Dynamoid
|
|
7
|
+
module Transactions
|
|
8
|
+
class Mutation
|
|
9
|
+
# @private
|
|
10
|
+
class Destroy < Base
|
|
11
|
+
def initialize(model, **options)
|
|
12
|
+
super()
|
|
13
|
+
|
|
14
|
+
@model = model
|
|
15
|
+
@options = options
|
|
16
|
+
@model_class = model.class
|
|
17
|
+
@aborted = false
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def on_registration
|
|
21
|
+
@aborted = true
|
|
22
|
+
@model.run_callbacks(:destroy) do
|
|
23
|
+
validate_primary_key!
|
|
24
|
+
|
|
25
|
+
@aborted = false
|
|
26
|
+
true
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
if @aborted && @options[:raise_error]
|
|
30
|
+
raise Dynamoid::Errors::RecordNotDestroyed, @model
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def on_commit
|
|
35
|
+
return if @aborted
|
|
36
|
+
|
|
37
|
+
@model.destroyed = true
|
|
38
|
+
@model.run_callbacks(:commit)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def on_rollback
|
|
42
|
+
@model.run_callbacks(:rollback)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def aborted?
|
|
46
|
+
@aborted
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def skipped?
|
|
50
|
+
false
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def observable_by_user_result
|
|
54
|
+
return false if @aborted
|
|
55
|
+
|
|
56
|
+
@model
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def action_requests
|
|
60
|
+
builder = Builders::DeleteRequestBuilder.new(@model_class)
|
|
61
|
+
builder.hash_key = dump_attribute(@model_class.hash_key, @model.hash_key)
|
|
62
|
+
builder.range_key = dump_attribute(@model_class.range_key, @model.range_value) if @model_class.range_key?
|
|
63
|
+
|
|
64
|
+
if @model_class.attributes[:lock_version]
|
|
65
|
+
lock_version = if @model.changes[:lock_version].nil?
|
|
66
|
+
@model.lock_version
|
|
67
|
+
else
|
|
68
|
+
@model.changes[:lock_version][0]
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# skip concurrency control when lock_version is nil
|
|
72
|
+
if lock_version
|
|
73
|
+
builder.add_expression_attribute_name('#_lock_version', 'lock_version')
|
|
74
|
+
builder.add_expression_attribute_value(':lock_version_value', lock_version)
|
|
75
|
+
builder.condition_expression = '#_lock_version = :lock_version_value'
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
[builder.request]
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
private
|
|
83
|
+
|
|
84
|
+
def validate_primary_key!
|
|
85
|
+
raise Dynamoid::Errors::MissingHashKey if @model.hash_key.nil?
|
|
86
|
+
raise Dynamoid::Errors::MissingRangeKey if @model_class.range_key? && @model.range_value.nil?
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def dump_attribute(name, value)
|
|
90
|
+
options = @model_class.attributes[name]
|
|
91
|
+
Dumping.dump_field(value, options)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base'
|
|
4
|
+
|
|
5
|
+
module Dynamoid
|
|
6
|
+
module Transactions
|
|
7
|
+
class Mutation
|
|
8
|
+
# @private
|
|
9
|
+
class Import < Base
|
|
10
|
+
def initialize(model_class, array_of_attributes)
|
|
11
|
+
super()
|
|
12
|
+
|
|
13
|
+
@model_class = model_class
|
|
14
|
+
@models = array_of_attributes.map do |attributes|
|
|
15
|
+
attributes = attributes.symbolize_keys
|
|
16
|
+
|
|
17
|
+
if @model_class.timestamps_enabled?
|
|
18
|
+
time_now = DateTime.now.in_time_zone(Time.zone)
|
|
19
|
+
attributes[:created_at] ||= time_now
|
|
20
|
+
attributes[:updated_at] ||= time_now
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
model = @model_class.build(attributes)
|
|
24
|
+
model.hash_key = SecureRandom.uuid if model.hash_key.nil?
|
|
25
|
+
model
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def on_registration
|
|
30
|
+
@models.each do |model|
|
|
31
|
+
validate_primary_key!(model)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def on_commit
|
|
36
|
+
@models.each do |model|
|
|
37
|
+
model.clear_changes_information
|
|
38
|
+
model.new_record = false
|
|
39
|
+
model.run_callbacks(:commit)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def on_rollback
|
|
44
|
+
@models.each do |model|
|
|
45
|
+
model.run_callbacks(:rollback)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def aborted?
|
|
50
|
+
false
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def skipped?
|
|
54
|
+
@models.empty?
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def observable_by_user_result
|
|
58
|
+
@models
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def action_requests
|
|
62
|
+
@models.map do |model|
|
|
63
|
+
attributes_dumped = Dynamoid::Dumping.dump_attributes(model.attributes, @model_class.attributes)
|
|
64
|
+
attributes_dumped = sanitize_item(attributes_dumped)
|
|
65
|
+
|
|
66
|
+
{
|
|
67
|
+
put: {
|
|
68
|
+
item: attributes_dumped,
|
|
69
|
+
table_name: @model_class.table_name
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
def validate_primary_key!(model)
|
|
78
|
+
raise Dynamoid::Errors::MissingHashKey if model.hash_key.nil?
|
|
79
|
+
raise Dynamoid::Errors::MissingRangeKey if @model_class.range_key? && model.range_value.nil?
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base'
|
|
4
|
+
require_relative 'builders/update_request_builder'
|
|
5
|
+
|
|
6
|
+
module Dynamoid
|
|
7
|
+
module Transactions
|
|
8
|
+
class Mutation
|
|
9
|
+
# @private
|
|
10
|
+
class Inc < Base
|
|
11
|
+
def initialize(model_class, hash_key, range_key, counters)
|
|
12
|
+
super()
|
|
13
|
+
|
|
14
|
+
@model_class = model_class
|
|
15
|
+
@hash_key = hash_key
|
|
16
|
+
@range_key = range_key
|
|
17
|
+
@counters = counters || {}
|
|
18
|
+
@touch = @counters.delete(:touch)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def on_registration
|
|
22
|
+
validate_primary_key!
|
|
23
|
+
validate_attribute_names!(@model_class, @counters.keys)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def on_commit; end
|
|
27
|
+
|
|
28
|
+
def on_rollback; end
|
|
29
|
+
|
|
30
|
+
def aborted?
|
|
31
|
+
false
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def skipped?
|
|
35
|
+
@counters.empty?
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def observable_by_user_result
|
|
39
|
+
nil
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def action_requests
|
|
43
|
+
builder = Builders::UpdateRequestBuilder.new(@model_class)
|
|
44
|
+
|
|
45
|
+
# primary key to look up an item to update
|
|
46
|
+
builder.hash_key = cast_and_dump(@model_class.hash_key, @hash_key)
|
|
47
|
+
builder.range_key = cast_and_dump(@model_class.range_key, @range_key) if @model_class.range_key?
|
|
48
|
+
|
|
49
|
+
# require primary key to exist
|
|
50
|
+
builder.add_expression_attribute_name('#_h', @model_class.hash_key)
|
|
51
|
+
condition_expression = 'attribute_exists(#_h)'
|
|
52
|
+
|
|
53
|
+
if @model_class.range_key?
|
|
54
|
+
builder.add_expression_attribute_name('#_r', @model_class.range_key)
|
|
55
|
+
condition_expression += ' AND attribute_exists(#_r)'
|
|
56
|
+
end
|
|
57
|
+
builder.condition_expression = condition_expression
|
|
58
|
+
|
|
59
|
+
@counters.each do |name, value|
|
|
60
|
+
builder.add_value(name, cast_and_dump(name, value))
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
if @touch
|
|
64
|
+
timestamp = DateTime.now.in_time_zone(Time.zone)
|
|
65
|
+
|
|
66
|
+
timestamp_attributes_to_touch.each do |name|
|
|
67
|
+
builder.set_attributes(name => dump(name, timestamp))
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
[builder.request]
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
def validate_primary_key!
|
|
77
|
+
raise Dynamoid::Errors::MissingHashKey if @hash_key.nil?
|
|
78
|
+
raise Dynamoid::Errors::MissingRangeKey if @model_class.range_key? && @range_key.nil?
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def timestamp_attributes_to_touch
|
|
82
|
+
names = []
|
|
83
|
+
names << :updated_at if @model_class.timestamps_enabled?
|
|
84
|
+
names += Array.wrap(@touch) if @touch != true
|
|
85
|
+
names
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def cast_and_dump(name, value)
|
|
89
|
+
options = @model_class.attributes[name]
|
|
90
|
+
value_casted = TypeCasting.cast_field(value, options)
|
|
91
|
+
Dumping.dump_field(value_casted, options)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def dump(name, value)
|
|
95
|
+
options = @model_class.attributes[name]
|
|
96
|
+
Dumping.dump_field(value, options)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|