dynamoid 3.9.0 → 3.11.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +26 -6
- data/README.md +202 -25
- data/dynamoid.gemspec +5 -6
- data/lib/dynamoid/adapter.rb +19 -13
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/create_table.rb +2 -2
- 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 +21 -2
- 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 +95 -66
- data/lib/dynamoid/associations/belongs_to.rb +6 -6
- data/lib/dynamoid/associations.rb +1 -1
- data/lib/dynamoid/components.rb +1 -0
- data/lib/dynamoid/config/options.rb +12 -12
- data/lib/dynamoid/config.rb +3 -0
- data/lib/dynamoid/criteria/chain.rb +149 -142
- 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 +87 -12
- data/lib/dynamoid/document.rb +1 -1
- data/lib/dynamoid/dumping.rb +38 -16
- data/lib/dynamoid/errors.rb +14 -2
- data/lib/dynamoid/fields/declare.rb +6 -6
- data/lib/dynamoid/fields.rb +6 -8
- data/lib/dynamoid/finders.rb +23 -32
- data/lib/dynamoid/indexes.rb +6 -7
- data/lib/dynamoid/loadable.rb +3 -2
- data/lib/dynamoid/persistence/inc.rb +6 -7
- 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 +17 -18
- data/lib/dynamoid/persistence/update_fields.rb +7 -5
- data/lib/dynamoid/persistence/update_validations.rb +1 -1
- data/lib/dynamoid/persistence/upsert.rb +5 -4
- data/lib/dynamoid/persistence.rb +77 -21
- 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 +60 -0
- data/lib/dynamoid/transaction_write/delete_with_primary_key.rb +59 -0
- data/lib/dynamoid/transaction_write/destroy.rb +79 -0
- data/lib/dynamoid/transaction_write/save.rb +164 -0
- data/lib/dynamoid/transaction_write/update_attributes.rb +46 -0
- data/lib/dynamoid/transaction_write/update_fields.rb +102 -0
- data/lib/dynamoid/transaction_write/upsert.rb +96 -0
- data/lib/dynamoid/transaction_write.rb +464 -0
- data/lib/dynamoid/type_casting.rb +18 -15
- data/lib/dynamoid/undumping.rb +14 -3
- data/lib/dynamoid/validations.rb +1 -1
- data/lib/dynamoid/version.rb +1 -1
- data/lib/dynamoid.rb +7 -0
- metadata +30 -16
- 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
@@ -36,8 +36,11 @@ module Dynamoid
|
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
39
|
-
def from_database(
|
40
|
-
super.tap
|
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[
|
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
|
-
|
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
|
-
@
|
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
|
-
@
|
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
|
-
|
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.
|
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
|
-
|
263
|
-
|
264
|
-
|
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
|
data/lib/dynamoid/document.rb
CHANGED
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
|
|
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
|
@@ -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
|
|
data/lib/dynamoid/fields.rb
CHANGED
@@ -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 [
|
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 ||=
|
267
|
-
|
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}="
|
380
|
+
setter = :"#{attribute_name}="
|
383
381
|
respond_to?(setter)
|
384
382
|
end
|
385
383
|
end
|
data/lib/dynamoid/finders.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
#
|
272
|
-
|
273
|
-
|
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
|
-
|
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
|
-
|
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
|
302
|
+
if finder.include?('all')
|
312
303
|
chain.all
|
313
304
|
else
|
314
305
|
chain.first
|
data/lib/dynamoid/indexes.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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
|
data/lib/dynamoid/loadable.rb
CHANGED
@@ -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.
|
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
|
-
|
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
|
-
|
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
|