dynamoid 1.3.4 → 2.2.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/.coveralls.yml +1 -0
- data/.gitignore +3 -0
- data/.travis.yml +37 -7
- data/Appraisals +11 -0
- data/CHANGELOG.md +115 -2
- data/Gemfile +2 -0
- data/LICENSE.txt +18 -16
- data/README.md +253 -34
- data/Rakefile +0 -24
- data/Vagrantfile +1 -1
- data/docker-compose.yml +7 -0
- data/dynamoid.gemspec +4 -4
- data/gemfiles/rails_4_0.gemfile +3 -3
- data/gemfiles/rails_4_1.gemfile +3 -3
- data/gemfiles/rails_4_2.gemfile +3 -3
- data/gemfiles/rails_5_0.gemfile +2 -1
- data/gemfiles/rails_5_1.gemfile +8 -0
- data/gemfiles/rails_5_2.gemfile +8 -0
- data/lib/dynamoid.rb +31 -31
- data/lib/dynamoid/adapter.rb +14 -10
- data/lib/dynamoid/adapter_plugin/aws_sdk_v2.rb +188 -100
- data/lib/dynamoid/associations.rb +21 -12
- data/lib/dynamoid/associations/association.rb +19 -3
- data/lib/dynamoid/associations/belongs_to.rb +26 -16
- data/lib/dynamoid/associations/has_and_belongs_to_many.rb +0 -16
- data/lib/dynamoid/associations/has_many.rb +2 -17
- data/lib/dynamoid/associations/has_one.rb +0 -14
- data/lib/dynamoid/associations/many_association.rb +19 -6
- data/lib/dynamoid/associations/single_association.rb +25 -7
- data/lib/dynamoid/config.rb +37 -18
- data/lib/dynamoid/config/backoff_strategies/constant_backoff.rb +11 -0
- data/lib/dynamoid/config/backoff_strategies/exponential_backoff.rb +25 -0
- data/lib/dynamoid/config/options.rb +1 -1
- data/lib/dynamoid/criteria/chain.rb +48 -32
- data/lib/dynamoid/dirty.rb +23 -4
- data/lib/dynamoid/document.rb +88 -5
- data/lib/dynamoid/errors.rb +4 -1
- data/lib/dynamoid/fields.rb +6 -6
- data/lib/dynamoid/finders.rb +42 -12
- data/lib/dynamoid/identity_map.rb +0 -1
- data/lib/dynamoid/indexes.rb +41 -54
- data/lib/dynamoid/persistence.rb +151 -40
- data/lib/dynamoid/railtie.rb +1 -1
- data/lib/dynamoid/validations.rb +4 -3
- data/lib/dynamoid/version.rb +1 -1
- metadata +18 -29
- data/gemfiles/rails_4_0.gemfile.lock +0 -150
- data/gemfiles/rails_4_1.gemfile.lock +0 -154
- data/gemfiles/rails_4_2.gemfile.lock +0 -175
- data/gemfiles/rails_5_0.gemfile.lock +0 -180
data/lib/dynamoid/fields.rb
CHANGED
@@ -23,7 +23,7 @@ module Dynamoid #:nodoc:
|
|
23
23
|
field :created_at, :datetime
|
24
24
|
field :updated_at, :datetime
|
25
25
|
|
26
|
-
field :id #Default primary key
|
26
|
+
field :id # Default primary key
|
27
27
|
end
|
28
28
|
|
29
29
|
module ClassMethods
|
@@ -50,7 +50,7 @@ module Dynamoid #:nodoc:
|
|
50
50
|
Dynamoid.logger.warn("Field type :float, which you declared for '#{name}', is deprecated in favor of :number.")
|
51
51
|
type = :number
|
52
52
|
end
|
53
|
-
self.attributes = attributes.merge(name => {:
|
53
|
+
self.attributes = attributes.merge(name => {type: type}.merge(options))
|
54
54
|
|
55
55
|
generated_methods.module_eval do
|
56
56
|
define_method(named) { read_attribute(named) }
|
@@ -67,13 +67,13 @@ module Dynamoid #:nodoc:
|
|
67
67
|
end
|
68
68
|
end
|
69
69
|
|
70
|
-
def range(name, type = :string)
|
71
|
-
field(name, type)
|
70
|
+
def range(name, type = :string, options = {})
|
71
|
+
field(name, type, options)
|
72
72
|
self.range_key = name
|
73
73
|
end
|
74
74
|
|
75
75
|
def table(options)
|
76
|
-
#a default 'id' column is created when Dynamoid::Document is included
|
76
|
+
# a default 'id' column is created when Dynamoid::Document is included
|
77
77
|
unless(attributes.has_key? hash_key)
|
78
78
|
remove_field :id
|
79
79
|
field(hash_key)
|
@@ -82,7 +82,7 @@ module Dynamoid #:nodoc:
|
|
82
82
|
|
83
83
|
def remove_field(field)
|
84
84
|
field = field.to_sym
|
85
|
-
attributes.delete(field) or raise
|
85
|
+
attributes.delete(field) or raise 'No such field'
|
86
86
|
|
87
87
|
generated_methods.module_eval do
|
88
88
|
remove_method field
|
data/lib/dynamoid/finders.rb
CHANGED
@@ -36,15 +36,27 @@ module Dynamoid
|
|
36
36
|
ids = Array(ids.flatten.uniq)
|
37
37
|
if ids.count == 1
|
38
38
|
result = self.find_by_id(ids.first, options)
|
39
|
+
if result.nil?
|
40
|
+
message = "Couldn't find #{self.name} with '#{self.hash_key}'=#{ids[0]}"
|
41
|
+
raise Errors::RecordNotFound.new(message)
|
42
|
+
end
|
39
43
|
expects_array ? Array(result) : result
|
40
44
|
else
|
41
|
-
find_all(ids)
|
45
|
+
result = find_all(ids)
|
46
|
+
if result.size != ids.size
|
47
|
+
message = "Couldn't find all #{self.name.pluralize} with '#{self.hash_key}': (#{ids.join(', ')}) "
|
48
|
+
message << "(found #{result.size} results, but was looking for #{ids.size})"
|
49
|
+
raise Errors::RecordNotFound.new(message)
|
50
|
+
end
|
51
|
+
result
|
42
52
|
end
|
43
53
|
end
|
44
54
|
|
45
|
-
# Return objects found by the given array of ids, either hash keys, or hash/range key combinations using
|
55
|
+
# Return objects found by the given array of ids, either hash keys, or hash/range key combinations using BatchGetItem.
|
46
56
|
# Returns empty array if no results found.
|
47
57
|
#
|
58
|
+
# Uses backoff specified by `Dynamoid::Config.backoff` config option
|
59
|
+
#
|
48
60
|
# @param [Array<ID>] ids
|
49
61
|
# @param [Hash] options: Passed to the underlying query.
|
50
62
|
#
|
@@ -55,8 +67,26 @@ module Dynamoid
|
|
55
67
|
# find all the tweets using hash key and range key with consistent read
|
56
68
|
# Tweet.find_all([['1', 'red'], ['1', 'green']], :consistent_read => true)
|
57
69
|
def find_all(ids, options = {})
|
58
|
-
|
59
|
-
|
70
|
+
results = unless Dynamoid.config.backoff
|
71
|
+
items = Dynamoid.adapter.read(self.table_name, ids, options)
|
72
|
+
items ? items[self.table_name] : []
|
73
|
+
else
|
74
|
+
items = []
|
75
|
+
backoff = nil
|
76
|
+
Dynamoid.adapter.read(self.table_name, ids, options) do |hash, has_unprocessed_items|
|
77
|
+
items += hash[self.table_name]
|
78
|
+
|
79
|
+
if has_unprocessed_items
|
80
|
+
backoff ||= Dynamoid.config.build_backoff
|
81
|
+
backoff.call
|
82
|
+
else
|
83
|
+
backoff = nil
|
84
|
+
end
|
85
|
+
end
|
86
|
+
items
|
87
|
+
end
|
88
|
+
|
89
|
+
results ? results.map {|i| from_database(i) } : []
|
60
90
|
end
|
61
91
|
|
62
92
|
# Find one object directly by id.
|
@@ -80,7 +110,7 @@ module Dynamoid
|
|
80
110
|
# @param [String/Number] range_key of the object to find
|
81
111
|
#
|
82
112
|
def find_by_composite_key(hash_key, range_key, options = {})
|
83
|
-
find_by_id(hash_key, options.merge(
|
113
|
+
find_by_id(hash_key, options.merge(range_key: range_key))
|
84
114
|
end
|
85
115
|
|
86
116
|
# Find all objects by hash and range keys.
|
@@ -105,7 +135,7 @@ module Dynamoid
|
|
105
135
|
# @return [Array] an array of all matching items
|
106
136
|
#
|
107
137
|
def find_all_by_composite_key(hash_key, options = {})
|
108
|
-
Dynamoid.adapter.query(self.table_name, options.merge(
|
138
|
+
Dynamoid.adapter.query(self.table_name, options.merge(hash_value: hash_key)).collect do |item|
|
109
139
|
from_database(item)
|
110
140
|
end
|
111
141
|
end
|
@@ -138,9 +168,9 @@ module Dynamoid
|
|
138
168
|
|
139
169
|
if range_key_field
|
140
170
|
range_key_field = range_key_field.to_s
|
141
|
-
range_key_op =
|
142
|
-
if range_key_field.include?(
|
143
|
-
range_key_field, range_key_op = range_key_field.split(
|
171
|
+
range_key_op = 'eq'
|
172
|
+
if range_key_field.include?('.')
|
173
|
+
range_key_field, range_key_op = range_key_field.split('.', 2)
|
144
174
|
end
|
145
175
|
range_op_mapped = RANGE_MAP.fetch(range_key_op)
|
146
176
|
end
|
@@ -151,9 +181,9 @@ module Dynamoid
|
|
151
181
|
|
152
182
|
# query
|
153
183
|
opts = {
|
154
|
-
:
|
155
|
-
:
|
156
|
-
:
|
184
|
+
hash_key: hash_key_field.to_s,
|
185
|
+
hash_value: hash_key_value,
|
186
|
+
index_name: index.name,
|
157
187
|
}
|
158
188
|
if range_key_field
|
159
189
|
opts[:range_key] = range_key_field
|
data/lib/dynamoid/indexes.rb
CHANGED
@@ -39,8 +39,8 @@ module Dynamoid
|
|
39
39
|
end
|
40
40
|
|
41
41
|
index_opts = {
|
42
|
-
:
|
43
|
-
:
|
42
|
+
read_capacity: Dynamoid::Config.read_capacity,
|
43
|
+
write_capacity: Dynamoid::Config.write_capacity
|
44
44
|
}.merge(options)
|
45
45
|
|
46
46
|
index_opts[:dynamoid_class] = self
|
@@ -52,7 +52,6 @@ module Dynamoid
|
|
52
52
|
self
|
53
53
|
end
|
54
54
|
|
55
|
-
|
56
55
|
# Defines a local secondary index on a table. Will use the same primary
|
57
56
|
# hash key as the table.
|
58
57
|
#
|
@@ -83,11 +82,10 @@ module Dynamoid
|
|
83
82
|
' must use a different :range_key than the primary key')
|
84
83
|
end
|
85
84
|
|
86
|
-
index_opts = options.merge(
|
87
|
-
:
|
88
|
-
:
|
89
|
-
:
|
90
|
-
})
|
85
|
+
index_opts = options.merge(
|
86
|
+
dynamoid_class: self,
|
87
|
+
type: :local_secondary,
|
88
|
+
hash_key: primary_hash_key)
|
91
89
|
|
92
90
|
index = Dynamoid::Indexes::Index.new(index_opts)
|
93
91
|
key = index_key(primary_hash_key, index_range_key)
|
@@ -95,13 +93,11 @@ module Dynamoid
|
|
95
93
|
self
|
96
94
|
end
|
97
95
|
|
98
|
-
|
99
96
|
def find_index(hash, range=nil)
|
100
97
|
index = self.indexes[index_key(hash, range)]
|
101
98
|
index
|
102
99
|
end
|
103
100
|
|
104
|
-
|
105
101
|
# Returns true iff the provided hash[,range] key combo is a local
|
106
102
|
# secondary index.
|
107
103
|
#
|
@@ -113,7 +109,6 @@ module Dynamoid
|
|
113
109
|
self.local_secondary_indexes[index_key(hash, range)].present?
|
114
110
|
end
|
115
111
|
|
116
|
-
|
117
112
|
# Returns true iff the provided hash[,range] key combo is a global
|
118
113
|
# secondary index.
|
119
114
|
#
|
@@ -125,7 +120,6 @@ module Dynamoid
|
|
125
120
|
self.global_secondary_indexes[index_key(hash, range)].present?
|
126
121
|
end
|
127
122
|
|
128
|
-
|
129
123
|
# Generates a convenient lookup key name for a hash/range index.
|
130
124
|
# Should normally not be used directly.
|
131
125
|
#
|
@@ -140,7 +134,6 @@ module Dynamoid
|
|
140
134
|
name
|
141
135
|
end
|
142
136
|
|
143
|
-
|
144
137
|
# Generates a default index name.
|
145
138
|
#
|
146
139
|
# @param [Symbol] hash hash key name.
|
@@ -150,7 +143,6 @@ module Dynamoid
|
|
150
143
|
"#{self.table_name}_index_#{self.index_key(hash, range)}"
|
151
144
|
end
|
152
145
|
|
153
|
-
|
154
146
|
# Convenience method to return all indexes on the table.
|
155
147
|
#
|
156
148
|
# @return [Hash<String, Object>] the combined hash of global and local
|
@@ -166,7 +158,6 @@ module Dynamoid
|
|
166
158
|
end
|
167
159
|
end
|
168
160
|
|
169
|
-
|
170
161
|
# Represents the attributes of a DynamoDB index.
|
171
162
|
class Index
|
172
163
|
include ActiveModel::Validations
|
@@ -178,7 +169,6 @@ module Dynamoid
|
|
178
169
|
:hash_key_schema, :range_key_schema, :projected_attributes,
|
179
170
|
:read_capacity, :write_capacity
|
180
171
|
|
181
|
-
|
182
172
|
validate do
|
183
173
|
validate_index_type
|
184
174
|
validate_hash_key
|
@@ -186,7 +176,6 @@ module Dynamoid
|
|
186
176
|
validate_projected_attributes
|
187
177
|
end
|
188
178
|
|
189
|
-
|
190
179
|
def initialize(attrs={})
|
191
180
|
unless attrs[:dynamoid_class].present?
|
192
181
|
raise Dynamoid::Errors::InvalidIndex.new(':dynamoid_class is required')
|
@@ -205,7 +194,6 @@ module Dynamoid
|
|
205
194
|
raise Dynamoid::Errors::InvalidIndex.new(self) unless self.valid?
|
206
195
|
end
|
207
196
|
|
208
|
-
|
209
197
|
# Convenience method to determine the projection type for an index.
|
210
198
|
# Projection types are: :keys_only, :all, :include.
|
211
199
|
#
|
@@ -218,56 +206,55 @@ module Dynamoid
|
|
218
206
|
end
|
219
207
|
end
|
220
208
|
|
221
|
-
|
222
209
|
private
|
223
210
|
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
end
|
211
|
+
def validate_projected_attributes
|
212
|
+
unless (@projected_attributes.is_a?(Array) ||
|
213
|
+
PROJECTION_TYPES.include?(@projected_attributes))
|
214
|
+
errors.add(:projected_attributes, 'Invalid projected attributes specified.')
|
229
215
|
end
|
216
|
+
end
|
230
217
|
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
end
|
218
|
+
def validate_index_type
|
219
|
+
unless (@type.present? &&
|
220
|
+
[:local_secondary, :global_secondary].include?(@type))
|
221
|
+
errors.add(:type, 'Invalid index :type specified')
|
236
222
|
end
|
223
|
+
end
|
237
224
|
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
else
|
248
|
-
errors.add(:range_key, 'Index :range_key is not a valid key type')
|
249
|
-
end
|
225
|
+
def validate_range_key
|
226
|
+
if @range_key.present?
|
227
|
+
range_field_attributes = @dynamoid_class.attributes[@range_key]
|
228
|
+
if range_field_attributes.present?
|
229
|
+
range_key_type = range_field_attributes[:type]
|
230
|
+
if Dynamoid::Fields::PERMITTED_KEY_TYPES.include?(range_key_type)
|
231
|
+
@range_key_schema = {
|
232
|
+
@range_key => @dynamoid_class.dynamo_type(range_key_type)
|
233
|
+
}
|
250
234
|
else
|
251
|
-
errors.add(:range_key,
|
235
|
+
errors.add(:range_key, 'Index :range_key is not a valid key type')
|
252
236
|
end
|
237
|
+
else
|
238
|
+
errors.add(:range_key, "No such field #{@range_key} defined on table")
|
253
239
|
end
|
254
240
|
end
|
241
|
+
end
|
255
242
|
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
else
|
265
|
-
errors.add(:hash_key, 'Index :hash_key is not a valid key type')
|
266
|
-
end
|
243
|
+
def validate_hash_key
|
244
|
+
hash_field_attributes = @dynamoid_class.attributes[@hash_key]
|
245
|
+
if hash_field_attributes.present?
|
246
|
+
hash_field_type = hash_field_attributes[:type]
|
247
|
+
if Dynamoid::Fields::PERMITTED_KEY_TYPES.include?(hash_field_type)
|
248
|
+
@hash_key_schema = {
|
249
|
+
@hash_key => @dynamoid_class.dynamo_type(hash_field_type)
|
250
|
+
}
|
267
251
|
else
|
268
|
-
errors.add(:hash_key,
|
252
|
+
errors.add(:hash_key, 'Index :hash_key is not a valid key type')
|
269
253
|
end
|
254
|
+
else
|
255
|
+
errors.add(:hash_key, "No such field #{@hash_key} defined on table")
|
270
256
|
end
|
257
|
+
end
|
271
258
|
end
|
272
259
|
end
|
273
260
|
end
|
data/lib/dynamoid/persistence.rb
CHANGED
@@ -21,7 +21,7 @@ module Dynamoid
|
|
21
21
|
table_base_name = options[:name] || base_class.name.split('::').last
|
22
22
|
.downcase.pluralize
|
23
23
|
|
24
|
-
@table_name ||= [Dynamoid::Config.namespace.to_s,table_base_name].reject(&:empty?).join(
|
24
|
+
@table_name ||= [Dynamoid::Config.namespace.to_s, table_base_name].reject(&:empty?).join('_')
|
25
25
|
end
|
26
26
|
|
27
27
|
# Creates a table.
|
@@ -41,14 +41,14 @@ module Dynamoid
|
|
41
41
|
range_key_hash = nil
|
42
42
|
end
|
43
43
|
options = {
|
44
|
-
:
|
45
|
-
:
|
46
|
-
:
|
47
|
-
:
|
48
|
-
:
|
49
|
-
:
|
50
|
-
:
|
51
|
-
:
|
44
|
+
id: self.hash_key,
|
45
|
+
table_name: self.table_name,
|
46
|
+
write_capacity: self.write_capacity,
|
47
|
+
read_capacity: self.read_capacity,
|
48
|
+
range_key: range_key_hash,
|
49
|
+
hash_key_type: dynamo_type(attributes[self.hash_key][:type]),
|
50
|
+
local_secondary_indexes: self.local_secondary_indexes.values,
|
51
|
+
global_secondary_indexes: self.global_secondary_indexes.values
|
52
52
|
}.merge(options)
|
53
53
|
|
54
54
|
Dynamoid.adapter.create_table(options[:table_name], options[:id], options)
|
@@ -74,9 +74,7 @@ module Dynamoid
|
|
74
74
|
if incoming.has_key?(attribute)
|
75
75
|
hash[attribute] = undump_field(incoming[attribute], options)
|
76
76
|
elsif options.has_key?(:default)
|
77
|
-
|
78
|
-
value = default_value.respond_to?(:call) ? default_value.call : default_value.dup
|
79
|
-
hash[attribute] = value
|
77
|
+
hash[attribute] = evaluate_default_value(options[:default])
|
80
78
|
else
|
81
79
|
hash[attribute] = nil
|
82
80
|
end
|
@@ -119,34 +117,22 @@ module Dynamoid
|
|
119
117
|
value
|
120
118
|
end
|
121
119
|
when :set
|
122
|
-
|
120
|
+
undump_set(options, value)
|
123
121
|
when :datetime
|
124
|
-
|
125
|
-
value
|
126
|
-
else
|
127
|
-
case Dynamoid::Config.application_timezone
|
128
|
-
when :utc
|
129
|
-
ActiveSupport::TimeZone['UTC'].at(value).to_datetime
|
130
|
-
when :local
|
131
|
-
Time.at(value).to_datetime
|
132
|
-
when String
|
133
|
-
ActiveSupport::TimeZone[Dynamoid::Config.application_timezone].at(value).to_datetime
|
134
|
-
end
|
135
|
-
end
|
122
|
+
parse_datetime(value, options)
|
136
123
|
when :date
|
137
124
|
if value.is_a?(Date) || value.is_a?(DateTime) || value.is_a?(Time)
|
138
125
|
value.to_date
|
139
126
|
else
|
140
|
-
|
127
|
+
parse_date(value, options)
|
141
128
|
end
|
142
129
|
when :boolean
|
143
|
-
# persisted as 't', but because undump is called during initialize it can come in as true
|
144
130
|
if value == 't' || value == true
|
145
131
|
true
|
146
132
|
elsif value == 'f' || value == false
|
147
133
|
false
|
148
134
|
else
|
149
|
-
raise ArgumentError,
|
135
|
+
raise ArgumentError, 'Boolean column neither true nor false'
|
150
136
|
end
|
151
137
|
else
|
152
138
|
raise ArgumentError, "Unknown type #{options[:type]}"
|
@@ -155,6 +141,17 @@ module Dynamoid
|
|
155
141
|
end
|
156
142
|
end
|
157
143
|
|
144
|
+
def undump_set(options, value)
|
145
|
+
case options[:of]
|
146
|
+
when :integer
|
147
|
+
value.map { |v| Integer(v) }.to_set
|
148
|
+
when :number
|
149
|
+
value.map { |v| BigDecimal.new(v.to_s) }.to_set
|
150
|
+
else
|
151
|
+
value.is_a?(Set) ? value : Set.new(value)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
158
155
|
def dump_field(value, options)
|
159
156
|
if (field_class = options[:type]).is_a?(Class)
|
160
157
|
if value.respond_to?(:dynamoid_dump)
|
@@ -177,15 +174,23 @@ module Dynamoid
|
|
177
174
|
when :array
|
178
175
|
!value.nil? ? value : nil
|
179
176
|
when :datetime
|
180
|
-
!value.nil? ? value
|
177
|
+
!value.nil? ? format_datetime(value, options) : nil
|
181
178
|
when :date
|
182
|
-
!value.nil? ? (value
|
179
|
+
!value.nil? ? format_date(value, options) : nil
|
183
180
|
when :serialized
|
184
181
|
options[:serializer] ? options[:serializer].dump(value) : value.to_yaml
|
185
182
|
when :raw
|
186
183
|
!value.nil? ? value : nil
|
187
184
|
when :boolean
|
188
|
-
!value.nil?
|
185
|
+
if !value.nil?
|
186
|
+
if options[:store_as_native_boolean]
|
187
|
+
!!value # native boolean type
|
188
|
+
else
|
189
|
+
value.to_s[0] # => "f" or "t"
|
190
|
+
end
|
191
|
+
else
|
192
|
+
nil
|
193
|
+
end
|
189
194
|
else
|
190
195
|
raise ArgumentError, "Unknown type #{options[:type]}"
|
191
196
|
end
|
@@ -207,6 +212,43 @@ module Dynamoid
|
|
207
212
|
end
|
208
213
|
end
|
209
214
|
|
215
|
+
# Creates several models at once.
|
216
|
+
# Neither callbacks nor validations run.
|
217
|
+
# It works efficiently because of using BatchWriteItem.
|
218
|
+
#
|
219
|
+
# Returns array of models
|
220
|
+
#
|
221
|
+
# Uses backoff specified by `Dynamoid::Config.backoff` config option
|
222
|
+
#
|
223
|
+
# @param [Array<Hash>] items
|
224
|
+
#
|
225
|
+
# @example
|
226
|
+
# User.import([{ name: 'a' }, { name: 'b' }])
|
227
|
+
def import(objects)
|
228
|
+
documents = objects.map do |attrs|
|
229
|
+
self.build(attrs).tap do |item|
|
230
|
+
item.hash_key = SecureRandom.uuid if item.hash_key.blank?
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
unless Dynamoid.config.backoff
|
235
|
+
Dynamoid.adapter.batch_write_item(self.table_name, documents.map(&:dump))
|
236
|
+
else
|
237
|
+
backoff = nil
|
238
|
+
Dynamoid.adapter.batch_write_item(self.table_name, documents.map(&:dump)) do |has_unprocessed_items|
|
239
|
+
if has_unprocessed_items
|
240
|
+
backoff ||= Dynamoid.config.build_backoff
|
241
|
+
backoff.call
|
242
|
+
else
|
243
|
+
backoff = nil
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
documents.each { |d| d.new_record = false }
|
249
|
+
documents
|
250
|
+
end
|
251
|
+
|
210
252
|
private
|
211
253
|
|
212
254
|
def undump_hash(hash)
|
@@ -231,6 +273,77 @@ module Dynamoid
|
|
231
273
|
val
|
232
274
|
end
|
233
275
|
end
|
276
|
+
|
277
|
+
def format_datetime(value, options)
|
278
|
+
use_string_format = options[:store_as_string].nil? \
|
279
|
+
? Dynamoid.config.store_datetime_as_string \
|
280
|
+
: options[:store_as_string]
|
281
|
+
|
282
|
+
if use_string_format
|
283
|
+
value.to_time.iso8601
|
284
|
+
else
|
285
|
+
unless value.respond_to?(:to_i) && value.respond_to?(:nsec)
|
286
|
+
value = value.to_time
|
287
|
+
end
|
288
|
+
BigDecimal("%d.%09d" % [value.to_i, value.nsec])
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
def format_date(value, options)
|
293
|
+
use_string_format = options[:store_as_string].nil? \
|
294
|
+
? Dynamoid.config.store_date_as_string \
|
295
|
+
: options[:store_as_string]
|
296
|
+
|
297
|
+
unless use_string_format
|
298
|
+
(value.to_date - UNIX_EPOCH_DATE).to_i
|
299
|
+
else
|
300
|
+
value.to_date.iso8601
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
def parse_datetime(value, options)
|
305
|
+
return value if value.is_a?(Date) || value.is_a?(DateTime) || value.is_a?(Time)
|
306
|
+
|
307
|
+
use_string_format = options[:store_as_string].nil? \
|
308
|
+
? Dynamoid.config.store_datetime_as_string \
|
309
|
+
: options[:store_as_string]
|
310
|
+
value = DateTime.iso8601(value).to_time.to_i if use_string_format
|
311
|
+
|
312
|
+
case Dynamoid::Config.application_timezone
|
313
|
+
when :utc
|
314
|
+
ActiveSupport::TimeZone['UTC'].at(value).to_datetime
|
315
|
+
when :local
|
316
|
+
Time.at(value).to_datetime
|
317
|
+
when String
|
318
|
+
ActiveSupport::TimeZone[Dynamoid::Config.application_timezone].at(value).to_datetime
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
def parse_date(value, options)
|
323
|
+
use_string_format = options[:store_as_string].nil? \
|
324
|
+
? Dynamoid.config.store_date_as_string \
|
325
|
+
: options[:store_as_string]
|
326
|
+
|
327
|
+
unless use_string_format
|
328
|
+
UNIX_EPOCH_DATE + value.to_i
|
329
|
+
else
|
330
|
+
Date.iso8601(value)
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
# Evaluates the default value given, this is used by undump
|
335
|
+
# when determining the value of the default given for a field options.
|
336
|
+
#
|
337
|
+
# @param [Object] :value the attribute's default value
|
338
|
+
def evaluate_default_value(val)
|
339
|
+
if val.respond_to?(:call)
|
340
|
+
val.call
|
341
|
+
elsif val.duplicable?
|
342
|
+
val.dup
|
343
|
+
else
|
344
|
+
val
|
345
|
+
end
|
346
|
+
end
|
234
347
|
end
|
235
348
|
|
236
349
|
# Set updated_at and any passed in field to current DateTime. Useful for things like last_login_at, etc.
|
@@ -256,15 +369,13 @@ module Dynamoid
|
|
256
369
|
self.class.create_table
|
257
370
|
|
258
371
|
if new_record?
|
259
|
-
conditions = { :
|
372
|
+
conditions = { unless_exists: [self.class.hash_key]}
|
260
373
|
conditions[:unless_exists] << range_key if(range_key)
|
261
374
|
|
262
375
|
run_callbacks(:create) { persist(conditions) }
|
263
376
|
else
|
264
377
|
persist
|
265
378
|
end
|
266
|
-
|
267
|
-
self
|
268
379
|
end
|
269
380
|
|
270
381
|
#
|
@@ -274,10 +385,10 @@ module Dynamoid
|
|
274
385
|
#
|
275
386
|
def update!(conditions = {}, &block)
|
276
387
|
run_callbacks(:update) do
|
277
|
-
options = range_key ? {:
|
388
|
+
options = range_key ? {range_key: dump_field(self.read_attribute(range_key), self.class.attributes[range_key])} : {}
|
278
389
|
|
279
390
|
begin
|
280
|
-
new_attrs = Dynamoid.adapter.update_item(self.class.table_name, self.hash_key, options.merge(:
|
391
|
+
new_attrs = Dynamoid.adapter.update_item(self.class.table_name, self.hash_key, options.merge(conditions: conditions)) do |t|
|
281
392
|
if(self.class.attributes[:lock_version])
|
282
393
|
t.add(lock_version: 1)
|
283
394
|
end
|
@@ -316,11 +427,11 @@ module Dynamoid
|
|
316
427
|
#
|
317
428
|
# @since 0.2.0
|
318
429
|
def delete
|
319
|
-
options = range_key ? {:
|
430
|
+
options = range_key ? {range_key: dump_field(self.read_attribute(range_key), self.class.attributes[range_key])} : {}
|
320
431
|
|
321
432
|
# Add an optimistic locking check if the lock_version column exists
|
322
433
|
if(self.class.attributes[:lock_version])
|
323
|
-
conditions = {:
|
434
|
+
conditions = {if: {}}
|
324
435
|
conditions[:if][:lock_version] =
|
325
436
|
if changes[:lock_version].nil?
|
326
437
|
self.lock_version
|
@@ -360,7 +471,7 @@ module Dynamoid
|
|
360
471
|
# @since 0.2.0
|
361
472
|
def persist(conditions = nil)
|
362
473
|
run_callbacks(:save) do
|
363
|
-
self.hash_key = SecureRandom.uuid if self.hash_key.
|
474
|
+
self.hash_key = SecureRandom.uuid if self.hash_key.blank?
|
364
475
|
|
365
476
|
# Add an exists check to prevent overwriting existing records with new ones
|
366
477
|
if(new_record?)
|
@@ -372,7 +483,7 @@ module Dynamoid
|
|
372
483
|
if(self.class.attributes[:lock_version])
|
373
484
|
conditions ||= {}
|
374
485
|
self.lock_version = (lock_version || 0) + 1
|
375
|
-
#Uses the original lock_version value from ActiveModel::Dirty in case user changed lock_version manually
|
486
|
+
# Uses the original lock_version value from ActiveModel::Dirty in case user changed lock_version manually
|
376
487
|
(conditions[:if] ||= {})[:lock_version] = changes[:lock_version][0] if(changes[:lock_version][0])
|
377
488
|
end
|
378
489
|
|