dynamoid 3.8.0 → 3.12.1
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 +89 -2
- data/README.md +375 -64
- data/SECURITY.md +17 -0
- data/dynamoid.gemspec +65 -0
- data/lib/dynamoid/adapter.rb +21 -14
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/create_table.rb +2 -2
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/execute_statement.rb +62 -0
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/filter_expression_convertor.rb +113 -0
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/item_updater.rb +29 -2
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/middleware/limit.rb +3 -0
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/projection_expression_convertor.rb +40 -0
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/query.rb +46 -61
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/scan.rb +34 -28
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/transact.rb +31 -0
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3.rb +132 -74
- data/lib/dynamoid/associations/belongs_to.rb +6 -6
- data/lib/dynamoid/associations.rb +1 -1
- data/lib/dynamoid/components.rb +3 -3
- data/lib/dynamoid/config/options.rb +12 -12
- data/lib/dynamoid/config.rb +4 -0
- data/lib/dynamoid/criteria/chain.rb +165 -149
- data/lib/dynamoid/criteria/key_fields_detector.rb +6 -7
- data/lib/dynamoid/criteria/nonexistent_fields_detector.rb +2 -2
- data/lib/dynamoid/criteria/where_conditions.rb +36 -0
- data/lib/dynamoid/dirty.rb +145 -59
- data/lib/dynamoid/document.rb +39 -3
- data/lib/dynamoid/dumping.rb +41 -19
- data/lib/dynamoid/errors.rb +32 -3
- data/lib/dynamoid/fields/declare.rb +6 -6
- data/lib/dynamoid/fields.rb +21 -29
- data/lib/dynamoid/finders.rb +68 -51
- data/lib/dynamoid/indexes.rb +7 -10
- data/lib/dynamoid/loadable.rb +3 -2
- data/lib/dynamoid/log/formatter.rb +19 -4
- data/lib/dynamoid/persistence/import.rb +4 -1
- data/lib/dynamoid/persistence/inc.rb +82 -0
- 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 +75 -17
- data/lib/dynamoid/persistence/update_fields.rb +24 -9
- data/lib/dynamoid/persistence/update_validations.rb +3 -3
- data/lib/dynamoid/persistence/upsert.rb +22 -8
- data/lib/dynamoid/persistence.rb +308 -72
- data/lib/dynamoid/transaction_read/find.rb +137 -0
- data/lib/dynamoid/transaction_read.rb +146 -0
- 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 +65 -0
- data/lib/dynamoid/transaction_write/delete_with_primary_key.rb +64 -0
- data/lib/dynamoid/transaction_write/destroy.rb +84 -0
- data/lib/dynamoid/transaction_write/item_updater.rb +55 -0
- data/lib/dynamoid/transaction_write/save.rb +169 -0
- data/lib/dynamoid/transaction_write/update_attributes.rb +46 -0
- data/lib/dynamoid/transaction_write/update_fields.rb +239 -0
- data/lib/dynamoid/transaction_write/upsert.rb +106 -0
- data/lib/dynamoid/transaction_write.rb +673 -0
- data/lib/dynamoid/type_casting.rb +18 -15
- data/lib/dynamoid/undumping.rb +14 -3
- data/lib/dynamoid/validations.rb +8 -5
- data/lib/dynamoid/version.rb +1 -1
- data/lib/dynamoid.rb +8 -0
- metadata +43 -49
- data/lib/dynamoid/criteria/ignored_conditions_detector.rb +0 -41
- data/lib/dynamoid/criteria/overwritten_conditions_detector.rb +0 -40
data/lib/dynamoid/dirty.rb
CHANGED
|
@@ -26,19 +26,20 @@ module Dynamoid
|
|
|
26
26
|
module ClassMethods
|
|
27
27
|
def update_fields(*)
|
|
28
28
|
super.tap do |model|
|
|
29
|
-
model.
|
|
29
|
+
model.clear_changes_information if model
|
|
30
30
|
end
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
def upsert(*)
|
|
34
34
|
super.tap do |model|
|
|
35
|
-
model.
|
|
35
|
+
model.clear_changes_information if model
|
|
36
36
|
end
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
-
def from_database(
|
|
40
|
-
super.tap do |
|
|
41
|
-
|
|
39
|
+
def from_database(attributes_from_database)
|
|
40
|
+
super.tap do |model|
|
|
41
|
+
model.clear_changes_information
|
|
42
|
+
model.assign_attributes_from_database(DeepDupper.dup_attributes(model.attributes, model.class))
|
|
42
43
|
end
|
|
43
44
|
end
|
|
44
45
|
end
|
|
@@ -110,7 +111,7 @@ module Dynamoid
|
|
|
110
111
|
#
|
|
111
112
|
# @return [ActiveSupport::HashWithIndifferentAccess]
|
|
112
113
|
def changes
|
|
113
|
-
ActiveSupport::HashWithIndifferentAccess[
|
|
114
|
+
ActiveSupport::HashWithIndifferentAccess[changed_attributes.map { |name, old_value| [name, [old_value, read_attribute(name)]] }]
|
|
114
115
|
end
|
|
115
116
|
|
|
116
117
|
# Returns a hash of attributes that were changed before the model was saved.
|
|
@@ -134,7 +135,31 @@ module Dynamoid
|
|
|
134
135
|
#
|
|
135
136
|
# @return [ActiveSupport::HashWithIndifferentAccess]
|
|
136
137
|
def changed_attributes
|
|
137
|
-
|
|
138
|
+
attributes_changed_by_setter.merge(attributes_changed_in_place)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Clear all dirty data: current changes and previous changes.
|
|
142
|
+
def clear_changes_information
|
|
143
|
+
@previously_changed = ActiveSupport::HashWithIndifferentAccess.new
|
|
144
|
+
@attributes_changed_by_setter = ActiveSupport::HashWithIndifferentAccess.new
|
|
145
|
+
@attributes_from_database = HashWithIndifferentAccess.new(DeepDupper.dup_attributes(@attributes, self.class))
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Clears dirty data and moves +changes+ to +previous_changes+.
|
|
149
|
+
def changes_applied
|
|
150
|
+
@previously_changed = changes
|
|
151
|
+
@attributes_changed_by_setter = ActiveSupport::HashWithIndifferentAccess.new
|
|
152
|
+
@attributes_from_database = HashWithIndifferentAccess.new(DeepDupper.dup_attributes(@attributes, self.class))
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Remove changes information for the provided attributes.
|
|
156
|
+
#
|
|
157
|
+
# @param attributes [Array[String]] - a list of attributes to clear changes for
|
|
158
|
+
def clear_attribute_changes(names)
|
|
159
|
+
attributes_changed_by_setter.except!(*names)
|
|
160
|
+
|
|
161
|
+
slice = HashWithIndifferentAccess.new(@attributes).slice(*names)
|
|
162
|
+
attributes_from_database.merge!(DeepDupper.dup_attributes(slice, self.class))
|
|
138
163
|
end
|
|
139
164
|
|
|
140
165
|
# Handle <tt>*_changed?</tt> for +method_missing+.
|
|
@@ -145,14 +170,14 @@ module Dynamoid
|
|
|
145
170
|
# person.attribute_changed?(:name, from: 'Alice', to: 'Bod')
|
|
146
171
|
#
|
|
147
172
|
# @private
|
|
148
|
-
# @param
|
|
173
|
+
# @param name [Symbol] attribute name
|
|
149
174
|
# @param options [Hash] conditions on +from+ and +to+ value (optional)
|
|
150
175
|
# @option options [Symbol] :from previous attribute value
|
|
151
176
|
# @option options [Symbol] :to current attribute value
|
|
152
|
-
def attribute_changed?(
|
|
153
|
-
result = changes_include?(
|
|
154
|
-
result &&= options[:to] ==
|
|
155
|
-
result &&= options[:from] == changed_attributes[
|
|
177
|
+
def attribute_changed?(name, options = {})
|
|
178
|
+
result = changes_include?(name)
|
|
179
|
+
result &&= options[:to] == read_attribute(name) if options.key?(:to)
|
|
180
|
+
result &&= options[:from] == changed_attributes[name] if options.key?(:from)
|
|
156
181
|
result
|
|
157
182
|
end
|
|
158
183
|
|
|
@@ -163,16 +188,16 @@ module Dynamoid
|
|
|
163
188
|
# person.attribute_was(:name) # => "Alice"
|
|
164
189
|
#
|
|
165
190
|
# @private
|
|
166
|
-
# @param
|
|
167
|
-
def attribute_was(
|
|
168
|
-
attribute_changed?(
|
|
191
|
+
# @param name [Symbol] attribute name
|
|
192
|
+
def attribute_was(name)
|
|
193
|
+
attribute_changed?(name) ? changed_attributes[name] : read_attribute(name)
|
|
169
194
|
end
|
|
170
195
|
|
|
171
196
|
# Restore all previous data of the provided attributes.
|
|
172
197
|
#
|
|
173
198
|
# @param attributes [Array[Symbol]] a list of attribute names
|
|
174
|
-
def restore_attributes(
|
|
175
|
-
|
|
199
|
+
def restore_attributes(names = changed)
|
|
200
|
+
names.each { |name| restore_attribute! name }
|
|
176
201
|
end
|
|
177
202
|
|
|
178
203
|
# Handles <tt>*_previously_changed?</tt> for +method_missing+.
|
|
@@ -183,10 +208,10 @@ module Dynamoid
|
|
|
183
208
|
# person.attribute_changed?(:name) # => true
|
|
184
209
|
#
|
|
185
210
|
# @private
|
|
186
|
-
# @param
|
|
211
|
+
# @param name [Symbol] attribute name
|
|
187
212
|
# @return [true|false]
|
|
188
|
-
def attribute_previously_changed?(
|
|
189
|
-
previous_changes_include?(
|
|
213
|
+
def attribute_previously_changed?(name)
|
|
214
|
+
previous_changes_include?(name)
|
|
190
215
|
end
|
|
191
216
|
|
|
192
217
|
# Handles <tt>*_previous_change</tt> for +method_missing+.
|
|
@@ -197,75 +222,136 @@ module Dynamoid
|
|
|
197
222
|
# person.attribute_previously_changed(:name) # => ["Alice", "Bob"]
|
|
198
223
|
#
|
|
199
224
|
# @private
|
|
200
|
-
# @param
|
|
225
|
+
# @param name [Symbol]
|
|
201
226
|
# @return [Array]
|
|
202
|
-
def attribute_previous_change(
|
|
203
|
-
previous_changes[
|
|
227
|
+
def attribute_previous_change(name)
|
|
228
|
+
previous_changes[name] if attribute_previously_changed?(name)
|
|
204
229
|
end
|
|
205
230
|
|
|
206
|
-
private
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
attributes_changed_by_setter.include?(attr_name)
|
|
231
|
+
# @private
|
|
232
|
+
def assign_attributes_from_database(attributes_from_database)
|
|
233
|
+
@attributes_from_database = HashWithIndifferentAccess.new(attributes_from_database)
|
|
210
234
|
end
|
|
211
|
-
alias attribute_changed_by_setter? changes_include?
|
|
212
235
|
|
|
213
|
-
|
|
214
|
-
def changes_applied # :doc:
|
|
215
|
-
@previously_changed = changes
|
|
216
|
-
@changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
|
|
217
|
-
end
|
|
236
|
+
private
|
|
218
237
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
@previously_changed = ActiveSupport::HashWithIndifferentAccess.new
|
|
222
|
-
@changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
|
|
238
|
+
def changes_include?(name)
|
|
239
|
+
attribute_changed_by_setter?(name) || attribute_changed_in_place?(name)
|
|
223
240
|
end
|
|
224
241
|
|
|
225
242
|
# Handle <tt>*_change</tt> for +method_missing+.
|
|
226
|
-
def attribute_change(
|
|
227
|
-
[changed_attributes[
|
|
243
|
+
def attribute_change(name)
|
|
244
|
+
[changed_attributes[name], read_attribute(name)] if attribute_changed?(name)
|
|
228
245
|
end
|
|
229
246
|
|
|
230
247
|
# Handle <tt>*_will_change!</tt> for +method_missing+.
|
|
231
|
-
def attribute_will_change!(
|
|
232
|
-
return if attribute_changed?(
|
|
248
|
+
def attribute_will_change!(name)
|
|
249
|
+
return if attribute_changed?(name)
|
|
233
250
|
|
|
234
251
|
begin
|
|
235
|
-
value =
|
|
236
|
-
value = value.
|
|
252
|
+
value = read_attribute(name)
|
|
253
|
+
value = value.clone if value.duplicable?
|
|
237
254
|
rescue TypeError, NoMethodError
|
|
238
255
|
end
|
|
239
256
|
|
|
240
|
-
set_attribute_was(
|
|
257
|
+
set_attribute_was(name, value)
|
|
241
258
|
end
|
|
242
259
|
|
|
243
260
|
# Handle <tt>restore_*!</tt> for +method_missing+.
|
|
244
|
-
def restore_attribute!(
|
|
245
|
-
if attribute_changed?(
|
|
246
|
-
|
|
247
|
-
clear_attribute_changes([
|
|
261
|
+
def restore_attribute!(name)
|
|
262
|
+
if attribute_changed?(name)
|
|
263
|
+
write_attribute(name, changed_attributes[name])
|
|
264
|
+
clear_attribute_changes([name])
|
|
248
265
|
end
|
|
249
266
|
end
|
|
250
267
|
|
|
251
|
-
# Returns +true+ if
|
|
268
|
+
# Returns +true+ if name were changed before the model was saved,
|
|
252
269
|
# +false+ otherwise.
|
|
253
|
-
def previous_changes_include?(
|
|
254
|
-
previous_changes.include?(
|
|
270
|
+
def previous_changes_include?(name)
|
|
271
|
+
previous_changes.include?(name)
|
|
255
272
|
end
|
|
256
273
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
274
|
+
def attributes_changed_by_setter
|
|
275
|
+
@attributes_changed_by_setter ||= ActiveSupport::HashWithIndifferentAccess.new
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
def attribute_changed_by_setter?(name)
|
|
279
|
+
attributes_changed_by_setter.include?(name)
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def attributes_from_database
|
|
283
|
+
@attributes_from_database ||= ActiveSupport::HashWithIndifferentAccess.new
|
|
284
|
+
end
|
|
260
285
|
|
|
261
286
|
# Force an attribute to have a particular "before" value
|
|
262
|
-
def set_attribute_was(
|
|
263
|
-
attributes_changed_by_setter[
|
|
287
|
+
def set_attribute_was(name, old_value)
|
|
288
|
+
attributes_changed_by_setter[name] = old_value
|
|
264
289
|
end
|
|
265
290
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
291
|
+
def attributes_changed_in_place
|
|
292
|
+
attributes_from_database.select do |name, _|
|
|
293
|
+
attribute_changed_in_place?(name)
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
def attribute_changed_in_place?(name)
|
|
298
|
+
return false if attribute_changed_by_setter?(name)
|
|
299
|
+
|
|
300
|
+
value_from_database = attributes_from_database[name]
|
|
301
|
+
return false if value_from_database.nil?
|
|
302
|
+
|
|
303
|
+
value = read_attribute(name)
|
|
304
|
+
type_options = self.class.attributes[name.to_sym]
|
|
305
|
+
|
|
306
|
+
unless type_options[:type].is_a?(Class) && !type_options[:comparable]
|
|
307
|
+
# common case
|
|
308
|
+
value != value_from_database
|
|
309
|
+
else
|
|
310
|
+
# objects of a custom type that does not implement its own `#==` method
|
|
311
|
+
# (that's declared by `comparable: false` or just not specifying the
|
|
312
|
+
# option `comparable`) are compared by comparing their dumps
|
|
313
|
+
dump = Dumping.dump_field(value, type_options)
|
|
314
|
+
dump_from_database = Dumping.dump_field(value_from_database, type_options)
|
|
315
|
+
dump != dump_from_database
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
module DeepDupper
|
|
320
|
+
def self.dup_attributes(attributes, klass)
|
|
321
|
+
attributes.map do |name, value|
|
|
322
|
+
type_options = klass.attributes[name.to_sym]
|
|
323
|
+
value_duplicate = dup_attribute(value, type_options)
|
|
324
|
+
[name, value_duplicate]
|
|
325
|
+
end.to_h
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
def self.dup_attribute(value, type_options)
|
|
329
|
+
type, of = type_options.values_at(:type, :of)
|
|
330
|
+
|
|
331
|
+
case value
|
|
332
|
+
when NilClass, TrueClass, FalseClass, Numeric, Symbol, IO
|
|
333
|
+
# Till Ruby 2.4 these immutable objects could not be duplicated.
|
|
334
|
+
# IO objects (used for the binary type) cannot be duplicated as well.
|
|
335
|
+
value
|
|
336
|
+
when Array
|
|
337
|
+
if of.is_a? Class
|
|
338
|
+
# custom type
|
|
339
|
+
value.map { |e| dup_attribute(e, type: of) }
|
|
340
|
+
else
|
|
341
|
+
value.deep_dup
|
|
342
|
+
end
|
|
343
|
+
when Set
|
|
344
|
+
Set.new(value.map { |e| dup_attribute(e, type: of) })
|
|
345
|
+
else
|
|
346
|
+
if type.is_a? Class
|
|
347
|
+
# custom type
|
|
348
|
+
dump = Dumping.dump_field(value, type_options)
|
|
349
|
+
Undumping.undump_field(dump.deep_dup, type_options)
|
|
350
|
+
else
|
|
351
|
+
value.deep_dup
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
end
|
|
269
355
|
end
|
|
270
356
|
end
|
|
271
357
|
end
|
data/lib/dynamoid/document.rb
CHANGED
|
@@ -160,6 +160,22 @@ module Dynamoid
|
|
|
160
160
|
end
|
|
161
161
|
end
|
|
162
162
|
|
|
163
|
+
attr_accessor :abstract_class
|
|
164
|
+
|
|
165
|
+
def abstract_class?
|
|
166
|
+
defined?(@abstract_class) && @abstract_class == true
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def sti_name
|
|
170
|
+
name
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def sti_class_for(type_name)
|
|
174
|
+
type_name.constantize
|
|
175
|
+
rescue NameError
|
|
176
|
+
raise Errors::SubclassNotFound, "STI subclass does not found. Subclass: '#{type_name}'"
|
|
177
|
+
end
|
|
178
|
+
|
|
163
179
|
# @private
|
|
164
180
|
def deep_subclasses
|
|
165
181
|
subclasses + subclasses.map(&:deep_subclasses).flatten
|
|
@@ -167,7 +183,7 @@ module Dynamoid
|
|
|
167
183
|
|
|
168
184
|
# @private
|
|
169
185
|
def choose_right_class(attrs)
|
|
170
|
-
attrs[inheritance_field] ? attrs[inheritance_field]
|
|
186
|
+
attrs[inheritance_field] ? sti_class_for(attrs[inheritance_field]) : self
|
|
171
187
|
end
|
|
172
188
|
end
|
|
173
189
|
|
|
@@ -206,7 +222,7 @@ module Dynamoid
|
|
|
206
222
|
load(attrs_with_defaults.merge(attrs_virtual))
|
|
207
223
|
|
|
208
224
|
if block
|
|
209
|
-
|
|
225
|
+
yield(self)
|
|
210
226
|
end
|
|
211
227
|
end
|
|
212
228
|
end
|
|
@@ -244,7 +260,7 @@ module Dynamoid
|
|
|
244
260
|
#
|
|
245
261
|
# @return [Integer]
|
|
246
262
|
def hash
|
|
247
|
-
hash_key
|
|
263
|
+
[hash_key, range_value].hash
|
|
248
264
|
end
|
|
249
265
|
|
|
250
266
|
# Return a model's hash key value.
|
|
@@ -278,6 +294,26 @@ module Dynamoid
|
|
|
278
294
|
end
|
|
279
295
|
end
|
|
280
296
|
|
|
297
|
+
def inspect
|
|
298
|
+
# attributes order is:
|
|
299
|
+
# - partition key
|
|
300
|
+
# - sort key
|
|
301
|
+
# - user defined attributes
|
|
302
|
+
# - timestamps - created_at/updated_at
|
|
303
|
+
names = [self.class.hash_key]
|
|
304
|
+
names << self.class.range_key if self.class.range_key
|
|
305
|
+
names += self.class.attributes.keys - names - %i[created_at updated_at]
|
|
306
|
+
names << :created_at if self.class.attributes.key?(:created_at)
|
|
307
|
+
names << :updated_at if self.class.attributes.key?(:updated_at)
|
|
308
|
+
|
|
309
|
+
inspection = names.map do |name|
|
|
310
|
+
value = read_attribute(name)
|
|
311
|
+
"#{name}: #{value.inspect}"
|
|
312
|
+
end.join(', ')
|
|
313
|
+
|
|
314
|
+
"#<#{self.class.name} #{inspection}>"
|
|
315
|
+
end
|
|
316
|
+
|
|
281
317
|
private
|
|
282
318
|
|
|
283
319
|
def dumped_range_value
|
data/lib/dynamoid/dumping.rb
CHANGED
|
@@ -70,7 +70,8 @@ module Dynamoid
|
|
|
70
70
|
end
|
|
71
71
|
|
|
72
72
|
def invalid_value?(value)
|
|
73
|
-
(value.is_a?(Set)
|
|
73
|
+
(value.is_a?(Set) && value.empty?) ||
|
|
74
|
+
(value.is_a?(String) && value.empty? && Config.store_empty_string_as_nil)
|
|
74
75
|
end
|
|
75
76
|
end
|
|
76
77
|
|
|
@@ -86,6 +87,12 @@ module Dynamoid
|
|
|
86
87
|
|
|
87
88
|
# string -> string
|
|
88
89
|
class StringDumper < Base
|
|
90
|
+
def process(string)
|
|
91
|
+
return nil if string.nil?
|
|
92
|
+
return nil if string.empty? && Config.store_empty_string_as_nil
|
|
93
|
+
|
|
94
|
+
string
|
|
95
|
+
end
|
|
89
96
|
end
|
|
90
97
|
|
|
91
98
|
# integer -> number
|
|
@@ -101,6 +108,8 @@ module Dynamoid
|
|
|
101
108
|
ALLOWED_TYPES = %i[string integer number date datetime serialized].freeze
|
|
102
109
|
|
|
103
110
|
def process(set)
|
|
111
|
+
return nil if set.is_a?(Set) && set.empty?
|
|
112
|
+
|
|
104
113
|
if @options.key?(:of)
|
|
105
114
|
process_typed_collection(set)
|
|
106
115
|
else
|
|
@@ -112,13 +121,13 @@ module Dynamoid
|
|
|
112
121
|
|
|
113
122
|
def process_typed_collection(set)
|
|
114
123
|
if allowed_type?
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
if element_type == :string
|
|
119
|
-
result.reject!(&:empty?)
|
|
124
|
+
# StringDumper may replace "" with nil so we cannot distinguish it from an explicit nil
|
|
125
|
+
if element_type == :string && Config.store_empty_string_as_nil
|
|
126
|
+
set.reject! { |s| s && s.empty? }
|
|
120
127
|
end
|
|
121
128
|
|
|
129
|
+
dumper = Dumping.find_dumper(element_options)
|
|
130
|
+
result = set.map { |el| dumper.process(el) }
|
|
122
131
|
result.to_set
|
|
123
132
|
else
|
|
124
133
|
raise ArgumentError, "Set element type #{element_type} isn't supported"
|
|
@@ -164,14 +173,13 @@ module Dynamoid
|
|
|
164
173
|
|
|
165
174
|
def process_typed_collection(array)
|
|
166
175
|
if allowed_type?
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
if element_type == :string
|
|
171
|
-
result.reject!(&:empty?)
|
|
176
|
+
# StringDumper may replace "" with nil so we cannot distinguish it from an explicit nil
|
|
177
|
+
if element_type == :string && Config.store_empty_string_as_nil
|
|
178
|
+
array.reject! { |s| s && s.empty? }
|
|
172
179
|
end
|
|
173
180
|
|
|
174
|
-
|
|
181
|
+
dumper = Dumping.find_dumper(element_options)
|
|
182
|
+
array.map { |el| dumper.process(el) }
|
|
175
183
|
else
|
|
176
184
|
raise ArgumentError, "Array element type #{element_type} isn't supported"
|
|
177
185
|
end
|
|
@@ -210,7 +218,7 @@ module Dynamoid
|
|
|
210
218
|
# datetime -> integer/string
|
|
211
219
|
class DateTimeDumper < Base
|
|
212
220
|
def process(value)
|
|
213
|
-
|
|
221
|
+
value.nil? ? nil : format_datetime(value, @options)
|
|
214
222
|
end
|
|
215
223
|
|
|
216
224
|
private
|
|
@@ -237,7 +245,7 @@ module Dynamoid
|
|
|
237
245
|
# date -> integer/string
|
|
238
246
|
class DateDumper < Base
|
|
239
247
|
def process(value)
|
|
240
|
-
|
|
248
|
+
value.nil? ? nil : format_date(value, @options)
|
|
241
249
|
end
|
|
242
250
|
|
|
243
251
|
private
|
|
@@ -289,10 +297,24 @@ module Dynamoid
|
|
|
289
297
|
end
|
|
290
298
|
end
|
|
291
299
|
|
|
292
|
-
# string ->
|
|
300
|
+
# string -> StringIO
|
|
293
301
|
class BinaryDumper < Base
|
|
294
302
|
def process(value)
|
|
295
|
-
|
|
303
|
+
store_as_binary = if @options[:store_as_native_binary].nil?
|
|
304
|
+
Dynamoid.config.store_binary_as_native
|
|
305
|
+
else
|
|
306
|
+
@options[:store_as_native_binary]
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
if store_as_binary
|
|
310
|
+
if value.is_a?(StringIO) || value.is_a?(IO)
|
|
311
|
+
value
|
|
312
|
+
else
|
|
313
|
+
StringIO.new(value)
|
|
314
|
+
end
|
|
315
|
+
else
|
|
316
|
+
Base64.strict_encode64(value)
|
|
317
|
+
end
|
|
296
318
|
end
|
|
297
319
|
end
|
|
298
320
|
|
|
@@ -301,10 +323,10 @@ module Dynamoid
|
|
|
301
323
|
def process(value)
|
|
302
324
|
field_class = @options[:type]
|
|
303
325
|
|
|
304
|
-
if
|
|
305
|
-
value.dynamoid_dump
|
|
306
|
-
elsif field_class.respond_to?(:dynamoid_dump)
|
|
326
|
+
if field_class.respond_to?(:dynamoid_dump)
|
|
307
327
|
field_class.dynamoid_dump(value)
|
|
328
|
+
elsif value.respond_to?(:dynamoid_dump)
|
|
329
|
+
value.dynamoid_dump
|
|
308
330
|
else
|
|
309
331
|
raise ArgumentError, "Neither #{field_class} nor #{value} supports serialization for Dynamoid."
|
|
310
332
|
end
|
data/lib/dynamoid/errors.rb
CHANGED
|
@@ -6,6 +6,7 @@ module Dynamoid
|
|
|
6
6
|
# Generic Dynamoid error
|
|
7
7
|
class Error < StandardError; end
|
|
8
8
|
|
|
9
|
+
class MissingHashKey < Error; end
|
|
9
10
|
class MissingRangeKey < Error; end
|
|
10
11
|
|
|
11
12
|
class MissingIndex < Error; end
|
|
@@ -15,18 +16,27 @@ module Dynamoid
|
|
|
15
16
|
class InvalidIndex < Error
|
|
16
17
|
def initialize(item)
|
|
17
18
|
if item.is_a? String
|
|
18
|
-
super
|
|
19
|
+
super
|
|
19
20
|
else
|
|
20
21
|
super("Validation failed: #{item.errors.full_messages.join(', ')}")
|
|
21
22
|
end
|
|
22
23
|
end
|
|
23
24
|
end
|
|
24
25
|
|
|
26
|
+
class RecordNotSaved < Error
|
|
27
|
+
attr_reader :record
|
|
28
|
+
|
|
29
|
+
def initialize(record)
|
|
30
|
+
super('Failed to save the item')
|
|
31
|
+
@record = record
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
25
35
|
class RecordNotDestroyed < Error
|
|
26
36
|
attr_reader :record
|
|
27
37
|
|
|
28
38
|
def initialize(record)
|
|
29
|
-
super('Failed to destroy item')
|
|
39
|
+
super('Failed to destroy the item')
|
|
30
40
|
@record = record
|
|
31
41
|
end
|
|
32
42
|
end
|
|
@@ -76,6 +86,25 @@ module Dynamoid
|
|
|
76
86
|
|
|
77
87
|
class UnsupportedKeyType < Error; end
|
|
78
88
|
|
|
79
|
-
class UnknownAttribute < Error
|
|
89
|
+
class UnknownAttribute < Error
|
|
90
|
+
attr_reader :model_class, :attribute_name
|
|
91
|
+
|
|
92
|
+
def initialize(model_class, attribute_name)
|
|
93
|
+
super("Attribute #{attribute_name} does not exist in #{model_class}")
|
|
94
|
+
|
|
95
|
+
@model_class = model_class
|
|
96
|
+
@attribute_name = attribute_name
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
class SubclassNotFound < Error; end
|
|
101
|
+
|
|
102
|
+
class Rollback < Error; end
|
|
103
|
+
|
|
104
|
+
class ScanProhibited < Error
|
|
105
|
+
def initialize(_msg = nil)
|
|
106
|
+
super('Scan operations prohibited. Modify Dynamoid::Config.error_on_scan to change this behavior.')
|
|
107
|
+
end
|
|
108
|
+
end
|
|
80
109
|
end
|
|
81
110
|
end
|
|
@@ -48,7 +48,7 @@ module Dynamoid
|
|
|
48
48
|
|
|
49
49
|
@source.generated_methods.module_eval do
|
|
50
50
|
define_method(name) { read_attribute(name) }
|
|
51
|
-
define_method("#{name}?") do
|
|
51
|
+
define_method(:"#{name}?") do
|
|
52
52
|
value = read_attribute(name)
|
|
53
53
|
case value
|
|
54
54
|
when true then true
|
|
@@ -57,8 +57,8 @@ module Dynamoid
|
|
|
57
57
|
!value.nil?
|
|
58
58
|
end
|
|
59
59
|
end
|
|
60
|
-
define_method("#{name}=") { |value| write_attribute(name, value) }
|
|
61
|
-
define_method("#{name}_before_type_cast") { read_attribute_before_type_cast(name) }
|
|
60
|
+
define_method(:"#{name}=") { |value| write_attribute(name, value) }
|
|
61
|
+
define_method(:"#{name}_before_type_cast") { read_attribute_before_type_cast(name) }
|
|
62
62
|
end
|
|
63
63
|
end
|
|
64
64
|
|
|
@@ -70,9 +70,9 @@ module Dynamoid
|
|
|
70
70
|
|
|
71
71
|
@source.generated_methods.module_eval do
|
|
72
72
|
alias_method alias_name, name
|
|
73
|
-
alias_method "#{alias_name}=", "#{name}="
|
|
74
|
-
alias_method "#{alias_name}?", "#{name}?"
|
|
75
|
-
alias_method "#{alias_name}_before_type_cast", "#{name}_before_type_cast"
|
|
73
|
+
alias_method :"#{alias_name}=", :"#{name}="
|
|
74
|
+
alias_method :"#{alias_name}?", :"#{name}?"
|
|
75
|
+
alias_method :"#{alias_name}_before_type_cast", :"#{name}_before_type_cast"
|
|
76
76
|
end
|
|
77
77
|
end
|
|
78
78
|
|