dynamoid 3.9.0 → 3.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +26 -6
  3. data/README.md +202 -25
  4. data/dynamoid.gemspec +5 -6
  5. data/lib/dynamoid/adapter.rb +19 -13
  6. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/create_table.rb +2 -2
  7. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/filter_expression_convertor.rb +113 -0
  8. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/item_updater.rb +21 -2
  9. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/projection_expression_convertor.rb +40 -0
  10. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/query.rb +46 -61
  11. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/scan.rb +34 -28
  12. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/transact.rb +31 -0
  13. data/lib/dynamoid/adapter_plugin/aws_sdk_v3.rb +95 -66
  14. data/lib/dynamoid/associations/belongs_to.rb +6 -6
  15. data/lib/dynamoid/associations.rb +1 -1
  16. data/lib/dynamoid/components.rb +1 -0
  17. data/lib/dynamoid/config/options.rb +12 -12
  18. data/lib/dynamoid/config.rb +3 -0
  19. data/lib/dynamoid/criteria/chain.rb +149 -142
  20. data/lib/dynamoid/criteria/key_fields_detector.rb +6 -7
  21. data/lib/dynamoid/criteria/nonexistent_fields_detector.rb +2 -2
  22. data/lib/dynamoid/criteria/where_conditions.rb +36 -0
  23. data/lib/dynamoid/dirty.rb +87 -12
  24. data/lib/dynamoid/document.rb +1 -1
  25. data/lib/dynamoid/dumping.rb +38 -16
  26. data/lib/dynamoid/errors.rb +14 -2
  27. data/lib/dynamoid/fields/declare.rb +6 -6
  28. data/lib/dynamoid/fields.rb +6 -8
  29. data/lib/dynamoid/finders.rb +23 -32
  30. data/lib/dynamoid/indexes.rb +6 -7
  31. data/lib/dynamoid/loadable.rb +3 -2
  32. data/lib/dynamoid/persistence/inc.rb +6 -7
  33. data/lib/dynamoid/persistence/item_updater_with_casting_and_dumping.rb +36 -0
  34. data/lib/dynamoid/persistence/item_updater_with_dumping.rb +33 -0
  35. data/lib/dynamoid/persistence/save.rb +17 -18
  36. data/lib/dynamoid/persistence/update_fields.rb +7 -5
  37. data/lib/dynamoid/persistence/update_validations.rb +1 -1
  38. data/lib/dynamoid/persistence/upsert.rb +5 -4
  39. data/lib/dynamoid/persistence.rb +77 -21
  40. data/lib/dynamoid/transaction_write/base.rb +47 -0
  41. data/lib/dynamoid/transaction_write/create.rb +49 -0
  42. data/lib/dynamoid/transaction_write/delete_with_instance.rb +60 -0
  43. data/lib/dynamoid/transaction_write/delete_with_primary_key.rb +59 -0
  44. data/lib/dynamoid/transaction_write/destroy.rb +79 -0
  45. data/lib/dynamoid/transaction_write/save.rb +164 -0
  46. data/lib/dynamoid/transaction_write/update_attributes.rb +46 -0
  47. data/lib/dynamoid/transaction_write/update_fields.rb +102 -0
  48. data/lib/dynamoid/transaction_write/upsert.rb +96 -0
  49. data/lib/dynamoid/transaction_write.rb +464 -0
  50. data/lib/dynamoid/type_casting.rb +18 -15
  51. data/lib/dynamoid/undumping.rb +14 -3
  52. data/lib/dynamoid/validations.rb +1 -1
  53. data/lib/dynamoid/version.rb +1 -1
  54. data/lib/dynamoid.rb +7 -0
  55. metadata +30 -16
  56. data/lib/dynamoid/criteria/ignored_conditions_detector.rb +0 -41
  57. data/lib/dynamoid/criteria/overwritten_conditions_detector.rb +0 -40
@@ -36,8 +36,11 @@ module Dynamoid
36
36
  end
37
37
  end
38
38
 
39
- def from_database(*)
40
- super.tap(&: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))
43
+ end
41
44
  end
42
45
  end
43
46
 
@@ -108,7 +111,7 @@ module Dynamoid
108
111
  #
109
112
  # @return [ActiveSupport::HashWithIndifferentAccess]
110
113
  def changes
111
- ActiveSupport::HashWithIndifferentAccess[changed.map { |name| [name, attribute_change(name)] }]
114
+ ActiveSupport::HashWithIndifferentAccess[changed_attributes.map { |name, old_value| [name, [old_value, read_attribute(name)]] }]
112
115
  end
113
116
 
114
117
  # Returns a hash of attributes that were changed before the model was saved.
@@ -132,19 +135,21 @@ module Dynamoid
132
135
  #
133
136
  # @return [ActiveSupport::HashWithIndifferentAccess]
134
137
  def changed_attributes
135
- @changed_attributes ||= ActiveSupport::HashWithIndifferentAccess.new
138
+ attributes_changed_by_setter.merge(attributes_changed_in_place)
136
139
  end
137
140
 
138
141
  # Clear all dirty data: current changes and previous changes.
139
142
  def clear_changes_information
140
143
  @previously_changed = ActiveSupport::HashWithIndifferentAccess.new
141
- @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
144
+ @attributes_changed_by_setter = ActiveSupport::HashWithIndifferentAccess.new
145
+ @attributes_from_database = HashWithIndifferentAccess.new(DeepDupper.dup_attributes(@attributes, self.class))
142
146
  end
143
147
 
144
148
  # Clears dirty data and moves +changes+ to +previous_changes+.
145
149
  def changes_applied
146
150
  @previously_changed = changes
147
- @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
151
+ @attributes_changed_by_setter = ActiveSupport::HashWithIndifferentAccess.new
152
+ @attributes_from_database = HashWithIndifferentAccess.new(DeepDupper.dup_attributes(@attributes, self.class))
148
153
  end
149
154
 
150
155
  # Remove changes information for the provided attributes.
@@ -152,6 +157,9 @@ module Dynamoid
152
157
  # @param attributes [Array[String]] - a list of attributes to clear changes for
153
158
  def clear_attribute_changes(names)
154
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))
155
163
  end
156
164
 
157
165
  # Handle <tt>*_changed?</tt> for +method_missing+.
@@ -220,12 +228,16 @@ module Dynamoid
220
228
  previous_changes[name] if attribute_previously_changed?(name)
221
229
  end
222
230
 
231
+ # @private
232
+ def assign_attributes_from_database(attributes_from_database)
233
+ @attributes_from_database = HashWithIndifferentAccess.new(attributes_from_database)
234
+ end
235
+
223
236
  private
224
237
 
225
238
  def changes_include?(name)
226
- attributes_changed_by_setter.include?(name)
239
+ attribute_changed_by_setter?(name) || attribute_changed_in_place?(name)
227
240
  end
228
- alias attribute_changed_by_setter? changes_include?
229
241
 
230
242
  # Handle <tt>*_change</tt> for +method_missing+.
231
243
  def attribute_change(name)
@@ -238,7 +250,7 @@ module Dynamoid
238
250
 
239
251
  begin
240
252
  value = read_attribute(name)
241
- value = value.duplicable? ? value.clone : value
253
+ value = value.clone if value.duplicable?
242
254
  rescue TypeError, NoMethodError
243
255
  end
244
256
 
@@ -259,13 +271,76 @@ module Dynamoid
259
271
  previous_changes.include?(name)
260
272
  end
261
273
 
262
- # This is necessary because `changed_attributes` might be overridden in
263
- # other implemntations (e.g. in `ActiveRecord`)
264
- 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
265
285
 
266
286
  # Force an attribute to have a particular "before" value
267
287
  def set_attribute_was(name, old_value)
268
288
  attributes_changed_by_setter[name] = old_value
269
289
  end
290
+
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
+ value != value_from_database
305
+ end
306
+
307
+ module DeepDupper
308
+ def self.dup_attributes(attributes, klass)
309
+ attributes.map do |name, value|
310
+ type_options = klass.attributes[name.to_sym]
311
+ value_duplicate = dup_attribute(value, type_options)
312
+ [name, value_duplicate]
313
+ end.to_h
314
+ end
315
+
316
+ def self.dup_attribute(value, type_options)
317
+ type, of = type_options.values_at(:type, :of)
318
+
319
+ case value
320
+ when NilClass, TrueClass, FalseClass, Numeric, Symbol, IO
321
+ # till Ruby 2.4 these immutable objects could not be duplicated
322
+ # IO objects cannot be duplicated - is used for binary fields
323
+ value
324
+ when String
325
+ value.dup
326
+ when Array
327
+ if of.is_a? Class # custom type
328
+ value.map { |e| dup_attribute(e, type: of) }
329
+ else
330
+ value.deep_dup
331
+ end
332
+ when Set
333
+ Set.new(value.map { |e| dup_attribute(e, type: of) })
334
+ when Hash
335
+ value.deep_dup
336
+ else
337
+ if type.is_a? Class # custom type
338
+ Marshal.load(Marshal.dump(value)) # dup instance variables
339
+ else
340
+ value.dup # date, datetime
341
+ end
342
+ end
343
+ end
344
+ end
270
345
  end
271
346
  end
@@ -260,7 +260,7 @@ module Dynamoid
260
260
  #
261
261
  # @return [Integer]
262
262
  def hash
263
- hash_key.hash ^ range_value.hash
263
+ [hash_key, range_value].hash
264
264
  end
265
265
 
266
266
  # Return a model's hash key 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
 
@@ -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
@@ -79,5 +89,7 @@ module Dynamoid
79
89
  class UnknownAttribute < Error; end
80
90
 
81
91
  class SubclassNotFound < Error; end
92
+
93
+ class Rollback < Error; end
82
94
  end
83
95
  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
@@ -362,7 +360,7 @@ module Dynamoid
362
360
  seconds = options[:after]
363
361
 
364
362
  if self[name].blank?
365
- send("#{name}=", Time.now.to_i + seconds)
363
+ send(:"#{name}=", Time.now.to_i + seconds)
366
364
  end
367
365
  end
368
366
  end
@@ -374,12 +372,12 @@ module Dynamoid
374
372
 
375
373
  type = self.class.inheritance_field
376
374
  if self.class.attributes[type] && send(type).nil?
377
- send("#{type}=", self.class.sti_name)
375
+ send(:"#{type}=", self.class.sti_name)
378
376
  end
379
377
  end
380
378
 
381
379
  def attribute_is_present_on_model?(attribute_name)
382
- setter = "#{attribute_name}=".to_sym
380
+ setter = :"#{attribute_name}="
383
381
  respond_to?(setter)
384
382
  end
385
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
  #
@@ -89,7 +78,7 @@ module Dynamoid
89
78
  # # Find all the tweets using hash key and range key with consistent read
90
79
  # Tweet.find_all([['1', 'red'], ['1', 'green']], consistent_read: true)
91
80
  def find_all(ids, options = {})
92
- ActiveSupport::Deprecation.warn('[Dynamoid] .find_all is deprecated! Call .find instead of')
81
+ Dynamoid.deprecator.warn('[Dynamoid] .find_all is deprecated! Call .find instead of')
93
82
 
94
83
  _find_all(ids, options)
95
84
  end
@@ -111,7 +100,7 @@ module Dynamoid
111
100
  #
112
101
  # @since 0.2.0
113
102
  def find_by_id(id, options = {})
114
- ActiveSupport::Deprecation.warn('[Dynamoid] .find_by_id is deprecated! Call .find instead of', caller[1..-1])
103
+ Dynamoid.deprecator.warn('[Dynamoid] .find_by_id is deprecated! Call .find instead of')
115
104
 
116
105
  _find_by_id(id, options)
117
106
  end
@@ -191,7 +180,7 @@ module Dynamoid
191
180
  # @param range_key [Scalar value] range key of the object to find
192
181
  #
193
182
  def find_by_composite_key(hash_key, range_key, options = {})
194
- ActiveSupport::Deprecation.warn('[Dynamoid] .find_by_composite_key is deprecated! Call .find instead of')
183
+ Dynamoid.deprecator.warn('[Dynamoid] .find_by_composite_key is deprecated! Call .find instead of')
195
184
 
196
185
  _find_by_id(hash_key, options.merge(range_key: range_key))
197
186
  end
@@ -218,7 +207,7 @@ module Dynamoid
218
207
  #
219
208
  # @return [Array] an array of all matching items
220
209
  def find_all_by_composite_key(hash_key, options = {})
221
- ActiveSupport::Deprecation.warn('[Dynamoid] .find_all_composite_key is deprecated! Call .where instead of')
210
+ Dynamoid.deprecator.warn('[Dynamoid] .find_all_composite_key is deprecated! Call .where instead of')
222
211
 
223
212
  Dynamoid.adapter.query(table_name, options.merge(hash_value: hash_key)).flat_map { |i| i }.collect do |item|
224
213
  from_database(item)
@@ -248,12 +237,11 @@ module Dynamoid
248
237
  # @param options [Hash] conditions on range key e.g. +{ "rank.lte": 10 }, query filter, projected keys, scan_index_forward etc.
249
238
  # @return [Array] an array of all matching items
250
239
  def find_all_by_secondary_index(hash, options = {})
251
- ActiveSupport::Deprecation.warn('[Dynamoid] .find_all_by_secondary_index is deprecated! Call .where instead of')
240
+ Dynamoid.deprecator.warn('[Dynamoid] .find_all_by_secondary_index is deprecated! Call .where instead of')
252
241
 
253
242
  range = options[:range] || {}
254
243
  hash_key_field, hash_key_value = hash.first
255
244
  range_key_field, range_key_value = range.first
256
- range_op_mapped = nil
257
245
 
258
246
  if range_key_field
259
247
  range_key_field = range_key_field.to_s
@@ -261,27 +249,30 @@ module Dynamoid
261
249
  if range_key_field.include?('.')
262
250
  range_key_field, range_key_op = range_key_field.split('.', 2)
263
251
  end
264
- range_op_mapped = RANGE_MAP.fetch(range_key_op)
265
252
  end
266
253
 
267
254
  # Find the index
268
255
  index = find_index(hash_key_field, range_key_field)
269
256
  raise Dynamoid::Errors::MissingIndex, "attempted to find #{[hash_key_field, range_key_field]}" if index.nil?
270
257
 
271
- # query
272
- opts = {
273
- hash_key: hash_key_field.to_s,
274
- hash_value: hash_key_value,
275
- index_name: index.name
276
- }
258
+ # Query
259
+ query_key_conditions = {}
260
+ query_key_conditions[hash_key_field.to_sym] = [[:eq, hash_key_value]]
277
261
  if range_key_field
278
- opts[:range_key] = range_key_field
279
- opts[range_op_mapped] = range_key_value
280
- end
281
- dynamo_options = opts.merge(options.reject { |key, _| key == :range })
282
- Dynamoid.adapter.query(table_name, dynamo_options).flat_map { |i| i }.map do |item|
283
- from_database(item)
262
+ query_key_conditions[range_key_field.to_sym] = [[range_key_op.to_sym, range_key_value]]
284
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) }
285
276
  end
286
277
 
287
278
  # Find using exciting method_missing finders attributes. Uses criteria
@@ -300,7 +291,7 @@ module Dynamoid
300
291
  def method_missing(method, *args)
301
292
  # Cannot use Symbol#start_with? because it was introduced in Ruby 2.7, but we support Ruby >= 2.3
302
293
  if method.to_s.start_with?('find')
303
- ActiveSupport::Deprecation.warn("[Dynamoid] .#{method} is deprecated! Call .where instead of")
294
+ Dynamoid.deprecator.warn("[Dynamoid] .#{method} is deprecated! Call .where instead of")
304
295
 
305
296
  finder = method.to_s.split('_by_').first
306
297
  attributes = method.to_s.split('_by_').last.split('_and_')
@@ -308,7 +299,7 @@ module Dynamoid
308
299
  chain = Dynamoid::Criteria::Chain.new(self)
309
300
  chain = chain.where({}.tap { |h| attributes.each_with_index { |attr, index| h[attr.to_sym] = args[index] } })
310
301
 
311
- if finder =~ /all/
302
+ if finder.include?('all')
312
303
  chain.all
313
304
  else
314
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
@@ -317,7 +316,7 @@ module Dynamoid
317
316
 
318
317
  key_dynamodb_type = dynamodb_type(key_field_attributes[:type], key_field_attributes)
319
318
  if PERMITTED_KEY_DYNAMODB_TYPES.include?(key_dynamodb_type)
320
- send("#{key_param}_schema=", { key_val => key_dynamodb_type })
319
+ send(:"#{key_param}_schema=", { key_val => key_dynamodb_type })
321
320
  else
322
321
  errors.add(key_param, "Index :#{key_param} is not a valid key type")
323
322
  end
@@ -6,11 +6,12 @@ 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
13
13
  end
14
+ alias assign_attributes load
14
15
 
15
16
  # Reload an object from the database -- if you suspect the object has changed in the data store and you need those
16
17
  # changes to be reflected immediately, you would call this method. This is a consistent read.
@@ -27,7 +28,7 @@ module Dynamoid
27
28
 
28
29
  self.attributes = self.class.find(hash_key, **options).attributes
29
30
 
30
- @associations.values.each(&:reset)
31
+ @associations.each_value(&:reset)
31
32
  @new_record = false
32
33
 
33
34
  self
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'item_updater_with_casting_and_dumping'
4
+
3
5
  module Dynamoid
4
6
  module Persistence
5
7
  # @private
@@ -21,15 +23,17 @@ module Dynamoid
21
23
  touch = @counters.delete(:touch)
22
24
 
23
25
  Dynamoid.adapter.update_item(@model_class.table_name, @hash_key, update_item_options) do |t|
26
+ item_updater = ItemUpdaterWithCastingAndDumping.new(@model_class, t)
27
+
24
28
  @counters.each do |name, value|
25
- t.add(name => cast_and_dump_attribute_value(name, value))
29
+ item_updater.add(name => value)
26
30
  end
27
31
 
28
32
  if touch
29
33
  value = DateTime.now.in_time_zone(Time.zone)
30
34
 
31
35
  timestamp_attributes_to_touch(touch).each do |name|
32
- t.set(name => cast_and_dump_attribute_value(name, value))
36
+ item_updater.set(name => value)
33
37
  end
34
38
  end
35
39
  end
@@ -48,11 +52,6 @@ module Dynamoid
48
52
  end
49
53
  end
50
54
 
51
- def cast_and_dump_attribute_value(name, value)
52
- value_casted = TypeCasting.cast_field(value, @model_class.attributes[name])
53
- Dumping.dump_field(value_casted, @model_class.attributes[name])
54
- end
55
-
56
55
  def timestamp_attributes_to_touch(touch)
57
56
  return [] unless touch
58
57
 
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dynamoid
4
+ module Persistence
5
+ # @private
6
+ class ItemUpdaterWithCastingAndDumping
7
+ def initialize(model_class, item_updater)
8
+ @model_class = model_class
9
+ @item_updater = item_updater
10
+ end
11
+
12
+ def add(attributes)
13
+ @item_updater.add(cast_and_dump(attributes))
14
+ end
15
+
16
+ def set(attributes)
17
+ @item_updater.set(cast_and_dump(attributes))
18
+ end
19
+
20
+ private
21
+
22
+ def cast_and_dump(attributes)
23
+ casted_and_dumped = {}
24
+
25
+ attributes.each do |name, value|
26
+ value_casted = TypeCasting.cast_field(value, @model_class.attributes[name])
27
+ value_dumped = Dumping.dump_field(value_casted, @model_class.attributes[name])
28
+
29
+ casted_and_dumped[name] = value_dumped
30
+ end
31
+
32
+ casted_and_dumped
33
+ end
34
+ end
35
+ end
36
+ end