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.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +89 -2
  3. data/README.md +375 -64
  4. data/SECURITY.md +17 -0
  5. data/dynamoid.gemspec +65 -0
  6. data/lib/dynamoid/adapter.rb +21 -14
  7. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/create_table.rb +2 -2
  8. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/execute_statement.rb +62 -0
  9. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/filter_expression_convertor.rb +113 -0
  10. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/item_updater.rb +29 -2
  11. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/middleware/limit.rb +3 -0
  12. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/projection_expression_convertor.rb +40 -0
  13. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/query.rb +46 -61
  14. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/scan.rb +34 -28
  15. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/transact.rb +31 -0
  16. data/lib/dynamoid/adapter_plugin/aws_sdk_v3.rb +132 -74
  17. data/lib/dynamoid/associations/belongs_to.rb +6 -6
  18. data/lib/dynamoid/associations.rb +1 -1
  19. data/lib/dynamoid/components.rb +3 -3
  20. data/lib/dynamoid/config/options.rb +12 -12
  21. data/lib/dynamoid/config.rb +4 -0
  22. data/lib/dynamoid/criteria/chain.rb +165 -149
  23. data/lib/dynamoid/criteria/key_fields_detector.rb +6 -7
  24. data/lib/dynamoid/criteria/nonexistent_fields_detector.rb +2 -2
  25. data/lib/dynamoid/criteria/where_conditions.rb +36 -0
  26. data/lib/dynamoid/dirty.rb +145 -59
  27. data/lib/dynamoid/document.rb +39 -3
  28. data/lib/dynamoid/dumping.rb +41 -19
  29. data/lib/dynamoid/errors.rb +32 -3
  30. data/lib/dynamoid/fields/declare.rb +6 -6
  31. data/lib/dynamoid/fields.rb +21 -29
  32. data/lib/dynamoid/finders.rb +68 -51
  33. data/lib/dynamoid/indexes.rb +7 -10
  34. data/lib/dynamoid/loadable.rb +3 -2
  35. data/lib/dynamoid/log/formatter.rb +19 -4
  36. data/lib/dynamoid/persistence/import.rb +4 -1
  37. data/lib/dynamoid/persistence/inc.rb +82 -0
  38. data/lib/dynamoid/persistence/item_updater_with_casting_and_dumping.rb +36 -0
  39. data/lib/dynamoid/persistence/item_updater_with_dumping.rb +33 -0
  40. data/lib/dynamoid/persistence/save.rb +75 -17
  41. data/lib/dynamoid/persistence/update_fields.rb +24 -9
  42. data/lib/dynamoid/persistence/update_validations.rb +3 -3
  43. data/lib/dynamoid/persistence/upsert.rb +22 -8
  44. data/lib/dynamoid/persistence.rb +308 -72
  45. data/lib/dynamoid/transaction_read/find.rb +137 -0
  46. data/lib/dynamoid/transaction_read.rb +146 -0
  47. data/lib/dynamoid/transaction_write/base.rb +47 -0
  48. data/lib/dynamoid/transaction_write/create.rb +49 -0
  49. data/lib/dynamoid/transaction_write/delete_with_instance.rb +65 -0
  50. data/lib/dynamoid/transaction_write/delete_with_primary_key.rb +64 -0
  51. data/lib/dynamoid/transaction_write/destroy.rb +84 -0
  52. data/lib/dynamoid/transaction_write/item_updater.rb +55 -0
  53. data/lib/dynamoid/transaction_write/save.rb +169 -0
  54. data/lib/dynamoid/transaction_write/update_attributes.rb +46 -0
  55. data/lib/dynamoid/transaction_write/update_fields.rb +239 -0
  56. data/lib/dynamoid/transaction_write/upsert.rb +106 -0
  57. data/lib/dynamoid/transaction_write.rb +673 -0
  58. data/lib/dynamoid/type_casting.rb +18 -15
  59. data/lib/dynamoid/undumping.rb +14 -3
  60. data/lib/dynamoid/validations.rb +8 -5
  61. data/lib/dynamoid/version.rb +1 -1
  62. data/lib/dynamoid.rb +8 -0
  63. metadata +43 -49
  64. data/lib/dynamoid/criteria/ignored_conditions_detector.rb +0 -41
  65. data/lib/dynamoid/criteria/overwritten_conditions_detector.rb +0 -40
@@ -26,19 +26,20 @@ module Dynamoid
26
26
  module ClassMethods
27
27
  def update_fields(*)
28
28
  super.tap do |model|
29
- model.send(:clear_changes_information) if 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.send(:clear_changes_information) if model
35
+ model.clear_changes_information if model
36
36
  end
37
37
  end
38
38
 
39
- def from_database(*)
40
- super.tap do |m|
41
- m.send(:clear_changes_information)
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[changed.map { |attr| [attr, attribute_change(attr)] }]
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
- @changed_attributes ||= ActiveSupport::HashWithIndifferentAccess.new
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 attr [Symbol] attribute name
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?(attr, options = {})
153
- result = changes_include?(attr)
154
- result &&= options[:to] == __send__(attr) if options.key?(:to)
155
- result &&= options[:from] == changed_attributes[attr] if options.key?(:from)
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 attr [Symbol] attribute name
167
- def attribute_was(attr)
168
- attribute_changed?(attr) ? changed_attributes[attr] : __send__(attr)
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(attributes = changed)
175
- attributes.each { |attr| restore_attribute! attr }
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 attr [Symbol] attribute name
211
+ # @param name [Symbol] attribute name
187
212
  # @return [true|false]
188
- def attribute_previously_changed?(attr)
189
- previous_changes_include?(attr)
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 attr [Symbol]
225
+ # @param name [Symbol]
201
226
  # @return [Array]
202
- def attribute_previous_change(attr)
203
- previous_changes[attr] if attribute_previously_changed?(attr)
227
+ def attribute_previous_change(name)
228
+ previous_changes[name] if attribute_previously_changed?(name)
204
229
  end
205
230
 
206
- private
207
-
208
- def changes_include?(attr_name)
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
- # Removes current changes and makes them accessible through +previous_changes+.
214
- def changes_applied # :doc:
215
- @previously_changed = changes
216
- @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
217
- end
236
+ private
218
237
 
219
- # Clear all dirty data: current changes and previous changes.
220
- def clear_changes_information # :doc:
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(attr)
227
- [changed_attributes[attr], __send__(attr)] if attribute_changed?(attr)
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!(attr)
232
- return if attribute_changed?(attr)
248
+ def attribute_will_change!(name)
249
+ return if attribute_changed?(name)
233
250
 
234
251
  begin
235
- value = __send__(attr)
236
- value = value.duplicable? ? value.clone : 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(attr, value)
257
+ set_attribute_was(name, value)
241
258
  end
242
259
 
243
260
  # Handle <tt>restore_*!</tt> for +method_missing+.
244
- def restore_attribute!(attr)
245
- if attribute_changed?(attr)
246
- __send__("#{attr}=", changed_attributes[attr])
247
- clear_attribute_changes([attr])
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 attr_name were changed before the model was saved,
268
+ # Returns +true+ if name were changed before the model was saved,
252
269
  # +false+ otherwise.
253
- def previous_changes_include?(attr_name)
254
- previous_changes.include?(attr_name)
270
+ def previous_changes_include?(name)
271
+ previous_changes.include?(name)
255
272
  end
256
273
 
257
- # This is necessary because `changed_attributes` might be overridden in
258
- # other implemntations (e.g. in `ActiveRecord`)
259
- alias attributes_changed_by_setter changed_attributes
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(attr, old_value)
263
- attributes_changed_by_setter[attr] = old_value
287
+ def set_attribute_was(name, old_value)
288
+ attributes_changed_by_setter[name] = old_value
264
289
  end
265
290
 
266
- # Remove changes information for the provided attributes.
267
- def clear_attribute_changes(attributes)
268
- attributes_changed_by_setter.except!(*attributes)
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
@@ -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].constantize : self
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
- block.call(self)
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.hash ^ range_value.hash
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
@@ -70,7 +70,8 @@ module Dynamoid
70
70
  end
71
71
 
72
72
  def invalid_value?(value)
73
- (value.is_a?(Set) || value.is_a?(String)) && value.empty?
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
- dumper = Dumping.find_dumper(element_options)
116
- result = set.map { |el| dumper.process(el) }
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
- dumper = Dumping.find_dumper(element_options)
168
- result = array.map { |el| dumper.process(el) }
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
- result
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
- !value.nil? ? format_datetime(value, @options) : nil
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
- !value.nil? ? format_date(value, @options) : nil
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 -> string
300
+ # string -> StringIO
293
301
  class BinaryDumper < Base
294
302
  def process(value)
295
- Base64.strict_encode64(value)
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 value.respond_to?(:dynamoid_dump)
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
@@ -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(item)
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; end
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