dynamoid 3.8.0 → 3.10.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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +54 -3
  3. data/README.md +111 -60
  4. data/SECURITY.md +17 -0
  5. data/dynamoid.gemspec +65 -0
  6. data/lib/dynamoid/adapter.rb +20 -13
  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 +78 -0
  10. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/item_updater.rb +28 -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 +38 -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 +33 -27
  15. data/lib/dynamoid/adapter_plugin/aws_sdk_v3.rb +116 -70
  16. data/lib/dynamoid/associations/belongs_to.rb +6 -6
  17. data/lib/dynamoid/associations.rb +1 -1
  18. data/lib/dynamoid/components.rb +2 -3
  19. data/lib/dynamoid/config/options.rb +12 -12
  20. data/lib/dynamoid/config.rb +1 -0
  21. data/lib/dynamoid/criteria/chain.rb +101 -138
  22. data/lib/dynamoid/criteria/key_fields_detector.rb +6 -7
  23. data/lib/dynamoid/criteria/nonexistent_fields_detector.rb +2 -2
  24. data/lib/dynamoid/criteria/where_conditions.rb +29 -0
  25. data/lib/dynamoid/dirty.rb +57 -57
  26. data/lib/dynamoid/document.rb +39 -3
  27. data/lib/dynamoid/dumping.rb +2 -2
  28. data/lib/dynamoid/errors.rb +2 -0
  29. data/lib/dynamoid/fields/declare.rb +6 -6
  30. data/lib/dynamoid/fields.rb +9 -27
  31. data/lib/dynamoid/finders.rb +26 -30
  32. data/lib/dynamoid/indexes.rb +7 -10
  33. data/lib/dynamoid/loadable.rb +2 -2
  34. data/lib/dynamoid/log/formatter.rb +19 -4
  35. data/lib/dynamoid/persistence/import.rb +4 -1
  36. data/lib/dynamoid/persistence/inc.rb +66 -0
  37. data/lib/dynamoid/persistence/save.rb +55 -12
  38. data/lib/dynamoid/persistence/update_fields.rb +2 -2
  39. data/lib/dynamoid/persistence/update_validations.rb +2 -2
  40. data/lib/dynamoid/persistence.rb +128 -48
  41. data/lib/dynamoid/type_casting.rb +15 -14
  42. data/lib/dynamoid/undumping.rb +1 -1
  43. data/lib/dynamoid/version.rb +1 -1
  44. metadata +27 -49
  45. data/lib/dynamoid/criteria/ignored_conditions_detector.rb +0 -41
  46. data/lib/dynamoid/criteria/overwritten_conditions_detector.rb +0 -40
@@ -26,20 +26,18 @@ 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
39
  def from_database(*)
40
- super.tap do |m|
41
- m.send(:clear_changes_information)
42
- end
40
+ super.tap(&:clear_changes_information)
43
41
  end
44
42
  end
45
43
 
@@ -110,7 +108,7 @@ module Dynamoid
110
108
  #
111
109
  # @return [ActiveSupport::HashWithIndifferentAccess]
112
110
  def changes
113
- ActiveSupport::HashWithIndifferentAccess[changed.map { |attr| [attr, attribute_change(attr)] }]
111
+ ActiveSupport::HashWithIndifferentAccess[changed.map { |name| [name, attribute_change(name)] }]
114
112
  end
115
113
 
116
114
  # Returns a hash of attributes that were changed before the model was saved.
@@ -137,6 +135,25 @@ module Dynamoid
137
135
  @changed_attributes ||= ActiveSupport::HashWithIndifferentAccess.new
138
136
  end
139
137
 
138
+ # Clear all dirty data: current changes and previous changes.
139
+ def clear_changes_information
140
+ @previously_changed = ActiveSupport::HashWithIndifferentAccess.new
141
+ @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
142
+ end
143
+
144
+ # Clears dirty data and moves +changes+ to +previous_changes+.
145
+ def changes_applied
146
+ @previously_changed = changes
147
+ @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
148
+ end
149
+
150
+ # Remove changes information for the provided attributes.
151
+ #
152
+ # @param attributes [Array[String]] - a list of attributes to clear changes for
153
+ def clear_attribute_changes(names)
154
+ attributes_changed_by_setter.except!(*names)
155
+ end
156
+
140
157
  # Handle <tt>*_changed?</tt> for +method_missing+.
141
158
  #
142
159
  # person.attribute_changed?(:name) # => true
@@ -145,14 +162,14 @@ module Dynamoid
145
162
  # person.attribute_changed?(:name, from: 'Alice', to: 'Bod')
146
163
  #
147
164
  # @private
148
- # @param attr [Symbol] attribute name
165
+ # @param name [Symbol] attribute name
149
166
  # @param options [Hash] conditions on +from+ and +to+ value (optional)
150
167
  # @option options [Symbol] :from previous attribute value
151
168
  # @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)
169
+ def attribute_changed?(name, options = {})
170
+ result = changes_include?(name)
171
+ result &&= options[:to] == read_attribute(name) if options.key?(:to)
172
+ result &&= options[:from] == changed_attributes[name] if options.key?(:from)
156
173
  result
157
174
  end
158
175
 
@@ -163,16 +180,16 @@ module Dynamoid
163
180
  # person.attribute_was(:name) # => "Alice"
164
181
  #
165
182
  # @private
166
- # @param attr [Symbol] attribute name
167
- def attribute_was(attr)
168
- attribute_changed?(attr) ? changed_attributes[attr] : __send__(attr)
183
+ # @param name [Symbol] attribute name
184
+ def attribute_was(name)
185
+ attribute_changed?(name) ? changed_attributes[name] : read_attribute(name)
169
186
  end
170
187
 
171
188
  # Restore all previous data of the provided attributes.
172
189
  #
173
190
  # @param attributes [Array[Symbol]] a list of attribute names
174
- def restore_attributes(attributes = changed)
175
- attributes.each { |attr| restore_attribute! attr }
191
+ def restore_attributes(names = changed)
192
+ names.each { |name| restore_attribute! name }
176
193
  end
177
194
 
178
195
  # Handles <tt>*_previously_changed?</tt> for +method_missing+.
@@ -183,10 +200,10 @@ module Dynamoid
183
200
  # person.attribute_changed?(:name) # => true
184
201
  #
185
202
  # @private
186
- # @param attr [Symbol] attribute name
203
+ # @param name [Symbol] attribute name
187
204
  # @return [true|false]
188
- def attribute_previously_changed?(attr)
189
- previous_changes_include?(attr)
205
+ def attribute_previously_changed?(name)
206
+ previous_changes_include?(name)
190
207
  end
191
208
 
192
209
  # Handles <tt>*_previous_change</tt> for +method_missing+.
@@ -197,61 +214,49 @@ module Dynamoid
197
214
  # person.attribute_previously_changed(:name) # => ["Alice", "Bob"]
198
215
  #
199
216
  # @private
200
- # @param attr [Symbol]
217
+ # @param name [Symbol]
201
218
  # @return [Array]
202
- def attribute_previous_change(attr)
203
- previous_changes[attr] if attribute_previously_changed?(attr)
219
+ def attribute_previous_change(name)
220
+ previous_changes[name] if attribute_previously_changed?(name)
204
221
  end
205
222
 
206
223
  private
207
224
 
208
- def changes_include?(attr_name)
209
- attributes_changed_by_setter.include?(attr_name)
225
+ def changes_include?(name)
226
+ attributes_changed_by_setter.include?(name)
210
227
  end
211
228
  alias attribute_changed_by_setter? changes_include?
212
229
 
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
218
-
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
223
- end
224
-
225
230
  # Handle <tt>*_change</tt> for +method_missing+.
226
- def attribute_change(attr)
227
- [changed_attributes[attr], __send__(attr)] if attribute_changed?(attr)
231
+ def attribute_change(name)
232
+ [changed_attributes[name], read_attribute(name)] if attribute_changed?(name)
228
233
  end
229
234
 
230
235
  # Handle <tt>*_will_change!</tt> for +method_missing+.
231
- def attribute_will_change!(attr)
232
- return if attribute_changed?(attr)
236
+ def attribute_will_change!(name)
237
+ return if attribute_changed?(name)
233
238
 
234
239
  begin
235
- value = __send__(attr)
236
- value = value.duplicable? ? value.clone : value
240
+ value = read_attribute(name)
241
+ value = value.clone if value.duplicable?
237
242
  rescue TypeError, NoMethodError
238
243
  end
239
244
 
240
- set_attribute_was(attr, value)
245
+ set_attribute_was(name, value)
241
246
  end
242
247
 
243
248
  # 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])
249
+ def restore_attribute!(name)
250
+ if attribute_changed?(name)
251
+ write_attribute(name, changed_attributes[name])
252
+ clear_attribute_changes([name])
248
253
  end
249
254
  end
250
255
 
251
- # Returns +true+ if attr_name were changed before the model was saved,
256
+ # Returns +true+ if name were changed before the model was saved,
252
257
  # +false+ otherwise.
253
- def previous_changes_include?(attr_name)
254
- previous_changes.include?(attr_name)
258
+ def previous_changes_include?(name)
259
+ previous_changes.include?(name)
255
260
  end
256
261
 
257
262
  # This is necessary because `changed_attributes` might be overridden in
@@ -259,13 +264,8 @@ module Dynamoid
259
264
  alias attributes_changed_by_setter changed_attributes
260
265
 
261
266
  # 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
264
- end
265
-
266
- # Remove changes information for the provided attributes.
267
- def clear_attribute_changes(attributes)
268
- attributes_changed_by_setter.except!(*attributes)
267
+ def set_attribute_was(name, old_value)
268
+ attributes_changed_by_setter[name] = old_value
269
269
  end
270
270
  end
271
271
  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
@@ -210,7 +210,7 @@ module Dynamoid
210
210
  # datetime -> integer/string
211
211
  class DateTimeDumper < Base
212
212
  def process(value)
213
- !value.nil? ? format_datetime(value, @options) : nil
213
+ value.nil? ? nil : format_datetime(value, @options)
214
214
  end
215
215
 
216
216
  private
@@ -237,7 +237,7 @@ module Dynamoid
237
237
  # date -> integer/string
238
238
  class DateDumper < Base
239
239
  def process(value)
240
- !value.nil? ? format_date(value, @options) : nil
240
+ value.nil? ? nil : format_date(value, @options)
241
241
  end
242
242
 
243
243
  private
@@ -77,5 +77,7 @@ module Dynamoid
77
77
  class UnsupportedKeyType < Error; end
78
78
 
79
79
  class UnknownAttribute < Error; end
80
+
81
+ class SubclassNotFound < Error; end
80
82
  end
81
83
  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
 
@@ -164,7 +164,7 @@ module Dynamoid
164
164
  #
165
165
  # @param name [Symbol] a range key attribute name
166
166
  # @param type [Symbol] a range key type (optional)
167
- # @param options [Symbol] type options (optional)
167
+ # @param options [Hash] type options (optional)
168
168
  def range(name, type = :string, options = {})
169
169
  field(name, type, options)
170
170
  self.range_key = name
@@ -263,10 +263,8 @@ module Dynamoid
263
263
 
264
264
  # @private
265
265
  def generated_methods
266
- @generated_methods ||= begin
267
- Module.new.tap do |mod|
268
- include(mod)
269
- end
266
+ @generated_methods ||= Module.new.tap do |mod|
267
+ include(mod)
270
268
  end
271
269
  end
272
270
  end
@@ -293,7 +291,7 @@ module Dynamoid
293
291
  old_value = read_attribute(name)
294
292
 
295
293
  unless attribute_is_present_on_model?(name)
296
- raise Dynamoid::Errors::UnknownAttribute.new("Attribute #{name} is not part of the model")
294
+ raise Dynamoid::Errors::UnknownAttribute, "Attribute #{name} is not part of the model"
297
295
  end
298
296
 
299
297
  if association = @associations[name]
@@ -354,23 +352,6 @@ module Dynamoid
354
352
 
355
353
  private
356
354
 
357
- # Automatically called during the created callback to set the created_at time.
358
- #
359
- # @since 0.2.0
360
- def set_created_at
361
- self.created_at ||= DateTime.now.in_time_zone(Time.zone) if self.class.timestamps_enabled?
362
- end
363
-
364
- # Automatically called during the save callback to set the updated_at time.
365
- #
366
- # @since 0.2.0
367
- def set_updated_at
368
- # @_touch_record=false means explicit disabling
369
- if self.class.timestamps_enabled? && changed? && !updated_at_changed? && @_touch_record != false
370
- self.updated_at = DateTime.now.in_time_zone(Time.zone)
371
- end
372
- end
373
-
374
355
  def set_expires_field
375
356
  options = self.class.options[:expires]
376
357
 
@@ -379,23 +360,24 @@ module Dynamoid
379
360
  seconds = options[:after]
380
361
 
381
362
  if self[name].blank?
382
- send("#{name}=", Time.now.to_i + seconds)
363
+ send(:"#{name}=", Time.now.to_i + seconds)
383
364
  end
384
365
  end
385
366
  end
386
367
 
387
368
  def set_inheritance_field
388
369
  # actually it does only following logic:
389
- # self.type ||= self.class.name if self.class.attributes[:type]
370
+ # self.type ||= self.class.sti_name if self.class.attributes[:type]
371
+ return if self.class.abstract_class?
390
372
 
391
373
  type = self.class.inheritance_field
392
374
  if self.class.attributes[type] && send(type).nil?
393
- send("#{type}=", self.class.name)
375
+ send(:"#{type}=", self.class.sti_name)
394
376
  end
395
377
  end
396
378
 
397
379
  def attribute_is_present_on_model?(attribute_name)
398
- setter = "#{attribute_name}=".to_sym
380
+ setter = :"#{attribute_name}="
399
381
  respond_to?(setter)
400
382
  end
401
383
  end
@@ -6,17 +6,6 @@ module Dynamoid
6
6
  module Finders
7
7
  extend ActiveSupport::Concern
8
8
 
9
- # @private
10
- RANGE_MAP = {
11
- 'gt' => :range_greater_than,
12
- 'lt' => :range_less_than,
13
- 'gte' => :range_gte,
14
- 'lte' => :range_lte,
15
- 'begins_with' => :range_begins_with,
16
- 'between' => :range_between,
17
- 'eq' => :range_eq
18
- }.freeze
19
-
20
9
  module ClassMethods
21
10
  # Find one or many objects, specified by one id or an array of ids.
22
11
  #
@@ -118,7 +107,7 @@ module Dynamoid
118
107
 
119
108
  # @private
120
109
  def _find_all(ids, options = {})
121
- raise Errors::MissingRangeKey if range_key && ids.any? { |pk, sk| sk.nil? }
110
+ raise Errors::MissingRangeKey if range_key && ids.any? { |_pk, sk| sk.nil? }
122
111
 
123
112
  if range_key
124
113
  ids = ids.map do |pk, sk|
@@ -151,7 +140,9 @@ module Dynamoid
151
140
  end
152
141
 
153
142
  if items.size == ids.size || !options[:raise_error]
154
- items ? items.map { |i| from_database(i) } : []
143
+ models = items ? items.map { |i| from_database(i) } : []
144
+ models.each { |m| m.run_callbacks :find }
145
+ models
155
146
  else
156
147
  ids_list = range_key ? ids.map { |pk, sk| "(#{pk},#{sk})" } : ids.map(&:to_s)
157
148
  message = "Couldn't find all #{name.pluralize} with primary keys [#{ids_list.join(', ')}] "
@@ -173,7 +164,9 @@ module Dynamoid
173
164
  end
174
165
 
175
166
  if item = Dynamoid.adapter.read(table_name, id, options.slice(:range_key, :consistent_read))
176
- from_database(item)
167
+ model = from_database(item)
168
+ model.run_callbacks :find
169
+ model
177
170
  elsif options[:raise_error]
178
171
  primary_key = range_key ? "(#{id},#{options[:range_key]})" : id
179
172
  message = "Couldn't find #{name} with primary key #{primary_key}"
@@ -249,7 +242,6 @@ module Dynamoid
249
242
  range = options[:range] || {}
250
243
  hash_key_field, hash_key_value = hash.first
251
244
  range_key_field, range_key_value = range.first
252
- range_op_mapped = nil
253
245
 
254
246
  if range_key_field
255
247
  range_key_field = range_key_field.to_s
@@ -257,27 +249,30 @@ module Dynamoid
257
249
  if range_key_field.include?('.')
258
250
  range_key_field, range_key_op = range_key_field.split('.', 2)
259
251
  end
260
- range_op_mapped = RANGE_MAP.fetch(range_key_op)
261
252
  end
262
253
 
263
254
  # Find the index
264
255
  index = find_index(hash_key_field, range_key_field)
265
256
  raise Dynamoid::Errors::MissingIndex, "attempted to find #{[hash_key_field, range_key_field]}" if index.nil?
266
257
 
267
- # query
268
- opts = {
269
- hash_key: hash_key_field.to_s,
270
- hash_value: hash_key_value,
271
- index_name: index.name
272
- }
258
+ # Query
259
+ query_key_conditions = {}
260
+ query_key_conditions[hash_key_field.to_sym] = [[:eq, hash_key_value]]
273
261
  if range_key_field
274
- opts[:range_key] = range_key_field
275
- opts[range_op_mapped] = range_key_value
276
- end
277
- dynamo_options = opts.merge(options.reject { |key, _| key == :range })
278
- Dynamoid.adapter.query(table_name, dynamo_options).flat_map { |i| i }.map do |item|
279
- from_database(item)
262
+ query_key_conditions[range_key_field.to_sym] = [[range_key_op.to_sym, range_key_value]]
280
263
  end
264
+
265
+ query_non_key_conditions = options
266
+ .except(*Dynamoid::AdapterPlugin::AwsSdkV3::Query::OPTIONS_KEYS)
267
+ .except(:range)
268
+ .symbolize_keys
269
+
270
+ query_options = options.slice(*Dynamoid::AdapterPlugin::AwsSdkV3::Query::OPTIONS_KEYS)
271
+ query_options[:index_name] = index.name
272
+
273
+ Dynamoid.adapter.query(table_name, query_key_conditions, query_non_key_conditions, query_options)
274
+ .flat_map { |i| i }
275
+ .map { |item| from_database(item) }
281
276
  end
282
277
 
283
278
  # Find using exciting method_missing finders attributes. Uses criteria
@@ -294,7 +289,8 @@ module Dynamoid
294
289
  # @private
295
290
  # @since 0.2.0
296
291
  def method_missing(method, *args)
297
- if method =~ /find/
292
+ # Cannot use Symbol#start_with? because it was introduced in Ruby 2.7, but we support Ruby >= 2.3
293
+ if method.to_s.start_with?('find')
298
294
  ActiveSupport::Deprecation.warn("[Dynamoid] .#{method} is deprecated! Call .where instead of")
299
295
 
300
296
  finder = method.to_s.split('_by_').first
@@ -303,7 +299,7 @@ module Dynamoid
303
299
  chain = Dynamoid::Criteria::Chain.new(self)
304
300
  chain = chain.where({}.tap { |h| attributes.each_with_index { |attr, index| h[attr.to_sym] = args[index] } })
305
301
 
306
- if finder =~ /all/
302
+ if finder.include?('all')
307
303
  chain.all
308
304
  else
309
305
  chain.first
@@ -130,13 +130,13 @@ module Dynamoid
130
130
  index_range_key = options[:range_key]
131
131
 
132
132
  unless index_range_key.present?
133
- raise Dynamoid::Errors::InvalidIndex, 'A local secondary index '\
134
- 'requires a :range_key to be specified'
133
+ raise Dynamoid::Errors::InvalidIndex, 'A local secondary index ' \
134
+ 'requires a :range_key to be specified'
135
135
  end
136
136
 
137
137
  if primary_range_key.present? && index_range_key == primary_range_key
138
- raise Dynamoid::Errors::InvalidIndex, 'A local secondary index'\
139
- ' must use a different :range_key than the primary key'
138
+ raise Dynamoid::Errors::InvalidIndex, 'A local secondary index ' \
139
+ 'must use a different :range_key than the primary key'
140
140
  end
141
141
 
142
142
  index_opts = options.merge(
@@ -159,8 +159,7 @@ module Dynamoid
159
159
  # @param range [scalar] the range key used to declare an index (optional)
160
160
  # @return [Dynamoid::Indexes::Index, nil] index object or nil if it isn't found
161
161
  def find_index(hash, range = nil)
162
- index = indexes[index_key(hash, range)]
163
- index
162
+ indexes[index_key(hash, range)]
164
163
  end
165
164
 
166
165
  # Returns an index by its name
@@ -169,10 +168,9 @@ module Dynamoid
169
168
  # @return [Dynamoid::Indexes::Index, nil] index object or nil if it isn't found
170
169
  def find_index_by_name(name)
171
170
  string_name = name.to_s
172
- indexes.each_value.detect{ |i| i.name.to_s == string_name }
171
+ indexes.each_value.detect { |i| i.name.to_s == string_name }
173
172
  end
174
173
 
175
-
176
174
  # Returns true iff the provided hash[,range] key combo is a local
177
175
  # secondary index.
178
176
  #
@@ -299,7 +297,6 @@ module Dynamoid
299
297
  end
300
298
  end
301
299
 
302
-
303
300
  def validate_hash_key
304
301
  validate_index_key(:hash_key, @hash_key)
305
302
  end
@@ -319,7 +316,7 @@ module Dynamoid
319
316
 
320
317
  key_dynamodb_type = dynamodb_type(key_field_attributes[:type], key_field_attributes)
321
318
  if PERMITTED_KEY_DYNAMODB_TYPES.include?(key_dynamodb_type)
322
- self.send("#{key_param}_schema=", { key_val => key_dynamodb_type })
319
+ send(:"#{key_param}_schema=", { key_val => key_dynamodb_type })
323
320
  else
324
321
  errors.add(key_param, "Index :#{key_param} is not a valid key type")
325
322
  end
@@ -6,7 +6,7 @@ module Dynamoid
6
6
 
7
7
  def load(attrs)
8
8
  attrs.each do |key, value|
9
- send("#{key}=", value) if respond_to?("#{key}=")
9
+ send(:"#{key}=", value) if respond_to?(:"#{key}=")
10
10
  end
11
11
 
12
12
  self
@@ -27,7 +27,7 @@ module Dynamoid
27
27
 
28
28
  self.attributes = self.class.find(hash_key, **options).attributes
29
29
 
30
- @associations.values.each(&:reset)
30
+ @associations.each_value(&:reset)
31
31
  @new_record = false
32
32
 
33
33
  self
@@ -1,10 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Dynamoid
2
4
  module Log
5
+ # https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/Log/Formatter.html
6
+ # https://docs.aws.amazon.com/sdk-for-ruby/v2/api/Seahorse/Client/Response.html
7
+ # https://aws.amazon.com/ru/blogs/developer/logging-requests/
3
8
  module Formatter
4
-
5
- # https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/Log/Formatter.html
6
- # https://docs.aws.amazon.com/sdk-for-ruby/v2/api/Seahorse/Client/Response.html
7
- # https://aws.amazon.com/ru/blogs/developer/logging-requests/
8
9
  class Debug
9
10
  def format(response)
10
11
  bold = "\x1b[1m"
@@ -21,6 +22,20 @@ module Dynamoid
21
22
  ].join("\n")
22
23
  end
23
24
  end
25
+
26
+ class Compact
27
+ def format(response)
28
+ bold = "\x1b[1m"
29
+ reset = "\x1b[0m"
30
+
31
+ [
32
+ response.context.operation.name,
33
+ bold,
34
+ response.context.http_request.body.string,
35
+ reset
36
+ ].join(' ')
37
+ end
38
+ end
24
39
  end
25
40
  end
26
41
  end
@@ -24,7 +24,10 @@ module Dynamoid
24
24
  import_with_backoff(models)
25
25
  end
26
26
 
27
- models.each { |d| d.new_record = false }
27
+ models.each do |m|
28
+ m.new_record = false
29
+ m.clear_changes_information
30
+ end
28
31
  models
29
32
  end
30
33