dynamoid 1.3.4 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|