dynamoid 3.9.0 → 3.11.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.
- 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
|