dynamoid 3.10.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 +14 -0
- data/README.md +182 -2
- data/dynamoid.gemspec +4 -4
- data/lib/dynamoid/adapter.rb +1 -1
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/filter_expression_convertor.rb +53 -18
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/item_updater.rb +5 -4
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/projection_expression_convertor.rb +9 -7
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/query.rb +1 -1
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/scan.rb +1 -1
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/transact.rb +31 -0
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3.rb +9 -5
- data/lib/dynamoid/components.rb +1 -0
- data/lib/dynamoid/config.rb +2 -0
- data/lib/dynamoid/criteria/chain.rb +63 -18
- data/lib/dynamoid/criteria/where_conditions.rb +13 -6
- data/lib/dynamoid/dirty.rb +86 -11
- data/lib/dynamoid/dumping.rb +36 -14
- data/lib/dynamoid/errors.rb +14 -2
- data/lib/dynamoid/finders.rb +6 -6
- data/lib/dynamoid/loadable.rb +1 -0
- 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 +5 -2
- data/lib/dynamoid/persistence/update_fields.rb +5 -3
- data/lib/dynamoid/persistence/upsert.rb +5 -4
- data/lib/dynamoid/persistence.rb +38 -17
- 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 +3 -1
- data/lib/dynamoid/undumping.rb +13 -2
- data/lib/dynamoid/validations.rb +1 -1
- data/lib/dynamoid/version.rb +1 -1
- data/lib/dynamoid.rb +7 -0
- metadata +18 -5
@@ -95,20 +95,27 @@ module Dynamoid
|
|
95
95
|
#
|
96
96
|
# Internally +where+ performs either +Scan+ or +Query+ operation.
|
97
97
|
#
|
98
|
+
# Conditions can be specified as an expression as well:
|
99
|
+
#
|
100
|
+
# Post.where('links_count = :v', v: 2)
|
101
|
+
#
|
102
|
+
# This way complex expressions can be constructed (e.g. with AND, OR, and NOT
|
103
|
+
# keyword):
|
104
|
+
#
|
105
|
+
# Address.where('city = :c AND (post_code = :pc1 OR post_code = :pc2)', city: 'A', pc1: '001', pc2: '002')
|
106
|
+
#
|
107
|
+
# See documentation for condition expression's syntax and examples:
|
108
|
+
# - https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html
|
109
|
+
# - https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Query.FilterExpression.html
|
110
|
+
#
|
98
111
|
# @return [Dynamoid::Criteria::Chain]
|
99
112
|
# @since 0.2.0
|
100
|
-
def where(
|
101
|
-
|
102
|
-
|
103
|
-
|
113
|
+
def where(conditions, placeholders = nil)
|
114
|
+
if conditions.is_a?(Hash)
|
115
|
+
where_with_hash(conditions)
|
116
|
+
else
|
117
|
+
where_with_string(conditions, placeholders)
|
104
118
|
end
|
105
|
-
|
106
|
-
@where_conditions.update(args.symbolize_keys)
|
107
|
-
|
108
|
-
# we should re-initialize keys detector every time we change @where_conditions
|
109
|
-
@key_fields_detector = KeyFieldsDetector.new(@where_conditions, @source, forced_index_name: @forced_index_name)
|
110
|
-
|
111
|
-
self
|
112
119
|
end
|
113
120
|
|
114
121
|
# Turns on strongly consistent reads.
|
@@ -500,6 +507,29 @@ module Dynamoid
|
|
500
507
|
|
501
508
|
private
|
502
509
|
|
510
|
+
def where_with_hash(conditions)
|
511
|
+
detector = NonexistentFieldsDetector.new(conditions, @source)
|
512
|
+
if detector.found?
|
513
|
+
Dynamoid.logger.warn(detector.warning_message)
|
514
|
+
end
|
515
|
+
|
516
|
+
@where_conditions.update_with_hash(conditions.symbolize_keys)
|
517
|
+
|
518
|
+
# we should re-initialize keys detector every time we change @where_conditions
|
519
|
+
@key_fields_detector = KeyFieldsDetector.new(@where_conditions, @source, forced_index_name: @forced_index_name)
|
520
|
+
|
521
|
+
self
|
522
|
+
end
|
523
|
+
|
524
|
+
def where_with_string(query, placeholders)
|
525
|
+
@where_conditions.update_with_string(query, placeholders)
|
526
|
+
|
527
|
+
# we should re-initialize keys detector every time we change @where_conditions
|
528
|
+
@key_fields_detector = KeyFieldsDetector.new(@where_conditions, @source, forced_index_name: @forced_index_name)
|
529
|
+
|
530
|
+
self
|
531
|
+
end
|
532
|
+
|
503
533
|
# The actual records referenced by the association.
|
504
534
|
#
|
505
535
|
# @return [Enumerator] an iterator of the found records.
|
@@ -635,12 +665,12 @@ module Dynamoid
|
|
635
665
|
end
|
636
666
|
|
637
667
|
def query_non_key_conditions
|
638
|
-
|
668
|
+
hash_conditions = {}
|
639
669
|
|
640
670
|
# Honor STI and :type field if it presents
|
641
671
|
if @source.attributes.key?(@source.inheritance_field) &&
|
642
672
|
@key_fields_detector.hash_key.to_sym != @source.inheritance_field.to_sym
|
643
|
-
@where_conditions.
|
673
|
+
@where_conditions.update_with_hash(sti_condition)
|
644
674
|
end
|
645
675
|
|
646
676
|
# TODO: Separate key conditions and non-key conditions properly:
|
@@ -650,11 +680,17 @@ module Dynamoid
|
|
650
680
|
.reject { |k, _| k.to_s =~ /^#{@key_fields_detector.range_key}\./ }
|
651
681
|
keys.each do |key|
|
652
682
|
name, condition = field_condition(key, @where_conditions[key])
|
653
|
-
|
654
|
-
|
683
|
+
hash_conditions[name] ||= []
|
684
|
+
hash_conditions[name] << condition
|
655
685
|
end
|
656
686
|
|
657
|
-
|
687
|
+
string_conditions = []
|
688
|
+
@where_conditions.string_conditions.each do |query, placeholders|
|
689
|
+
placeholders ||= {}
|
690
|
+
string_conditions << [query, placeholders]
|
691
|
+
end
|
692
|
+
|
693
|
+
[hash_conditions] + string_conditions
|
658
694
|
end
|
659
695
|
|
660
696
|
# TODO: casting should be operator aware
|
@@ -721,16 +757,25 @@ module Dynamoid
|
|
721
757
|
def scan_conditions
|
722
758
|
# Honor STI and :type field if it presents
|
723
759
|
if sti_condition
|
724
|
-
@where_conditions.
|
760
|
+
@where_conditions.update_with_hash(sti_condition)
|
725
761
|
end
|
726
762
|
|
727
|
-
{}
|
763
|
+
hash_conditions = {}
|
764
|
+
hash_conditions.tap do |opts|
|
728
765
|
@where_conditions.keys.map(&:to_sym).each do |key|
|
729
766
|
name, condition = field_condition(key, @where_conditions[key])
|
730
767
|
opts[name] ||= []
|
731
768
|
opts[name] << condition
|
732
769
|
end
|
733
770
|
end
|
771
|
+
|
772
|
+
string_conditions = []
|
773
|
+
@where_conditions.string_conditions.each do |query, placeholders|
|
774
|
+
placeholders ||= {}
|
775
|
+
string_conditions << [query, placeholders]
|
776
|
+
end
|
777
|
+
|
778
|
+
[hash_conditions] + string_conditions
|
734
779
|
end
|
735
780
|
|
736
781
|
def scan_options
|
@@ -4,24 +4,31 @@ module Dynamoid
|
|
4
4
|
module Criteria
|
5
5
|
# @private
|
6
6
|
class WhereConditions
|
7
|
+
attr_reader :string_conditions
|
8
|
+
|
7
9
|
def initialize
|
8
|
-
@
|
10
|
+
@hash_conditions = []
|
11
|
+
@string_conditions = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def update_with_hash(hash)
|
15
|
+
@hash_conditions << hash.symbolize_keys
|
9
16
|
end
|
10
17
|
|
11
|
-
def
|
12
|
-
@
|
18
|
+
def update_with_string(query, placeholders)
|
19
|
+
@string_conditions << [query, placeholders]
|
13
20
|
end
|
14
21
|
|
15
22
|
def keys
|
16
|
-
@
|
23
|
+
@hash_conditions.flat_map(&:keys)
|
17
24
|
end
|
18
25
|
|
19
26
|
def empty?
|
20
|
-
@
|
27
|
+
@hash_conditions.empty? && @string_conditions.empty?
|
21
28
|
end
|
22
29
|
|
23
30
|
def [](key)
|
24
|
-
hash = @
|
31
|
+
hash = @hash_conditions.find { |h| h.key?(key) }
|
25
32
|
hash[key] if hash
|
26
33
|
end
|
27
34
|
end
|
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)
|
@@ -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/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
|
@@ -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
|
data/lib/dynamoid/finders.rb
CHANGED
@@ -78,7 +78,7 @@ module Dynamoid
|
|
78
78
|
# # Find all the tweets using hash key and range key with consistent read
|
79
79
|
# Tweet.find_all([['1', 'red'], ['1', 'green']], consistent_read: true)
|
80
80
|
def find_all(ids, options = {})
|
81
|
-
|
81
|
+
Dynamoid.deprecator.warn('[Dynamoid] .find_all is deprecated! Call .find instead of')
|
82
82
|
|
83
83
|
_find_all(ids, options)
|
84
84
|
end
|
@@ -100,7 +100,7 @@ module Dynamoid
|
|
100
100
|
#
|
101
101
|
# @since 0.2.0
|
102
102
|
def find_by_id(id, options = {})
|
103
|
-
|
103
|
+
Dynamoid.deprecator.warn('[Dynamoid] .find_by_id is deprecated! Call .find instead of')
|
104
104
|
|
105
105
|
_find_by_id(id, options)
|
106
106
|
end
|
@@ -180,7 +180,7 @@ module Dynamoid
|
|
180
180
|
# @param range_key [Scalar value] range key of the object to find
|
181
181
|
#
|
182
182
|
def find_by_composite_key(hash_key, range_key, options = {})
|
183
|
-
|
183
|
+
Dynamoid.deprecator.warn('[Dynamoid] .find_by_composite_key is deprecated! Call .find instead of')
|
184
184
|
|
185
185
|
_find_by_id(hash_key, options.merge(range_key: range_key))
|
186
186
|
end
|
@@ -207,7 +207,7 @@ module Dynamoid
|
|
207
207
|
#
|
208
208
|
# @return [Array] an array of all matching items
|
209
209
|
def find_all_by_composite_key(hash_key, options = {})
|
210
|
-
|
210
|
+
Dynamoid.deprecator.warn('[Dynamoid] .find_all_composite_key is deprecated! Call .where instead of')
|
211
211
|
|
212
212
|
Dynamoid.adapter.query(table_name, options.merge(hash_value: hash_key)).flat_map { |i| i }.collect do |item|
|
213
213
|
from_database(item)
|
@@ -237,7 +237,7 @@ module Dynamoid
|
|
237
237
|
# @param options [Hash] conditions on range key e.g. +{ "rank.lte": 10 }, query filter, projected keys, scan_index_forward etc.
|
238
238
|
# @return [Array] an array of all matching items
|
239
239
|
def find_all_by_secondary_index(hash, options = {})
|
240
|
-
|
240
|
+
Dynamoid.deprecator.warn('[Dynamoid] .find_all_by_secondary_index is deprecated! Call .where instead of')
|
241
241
|
|
242
242
|
range = options[:range] || {}
|
243
243
|
hash_key_field, hash_key_value = hash.first
|
@@ -291,7 +291,7 @@ module Dynamoid
|
|
291
291
|
def method_missing(method, *args)
|
292
292
|
# Cannot use Symbol#start_with? because it was introduced in Ruby 2.7, but we support Ruby >= 2.3
|
293
293
|
if method.to_s.start_with?('find')
|
294
|
-
|
294
|
+
Dynamoid.deprecator.warn("[Dynamoid] .#{method} is deprecated! Call .where instead of")
|
295
295
|
|
296
296
|
finder = method.to_s.split('_by_').first
|
297
297
|
attributes = method.to_s.split('_by_').last.split('_and_')
|
data/lib/dynamoid/loadable.rb
CHANGED
@@ -11,6 +11,7 @@ module Dynamoid
|
|
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.
|
@@ -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
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dynamoid
|
4
|
+
module Persistence
|
5
|
+
# @private
|
6
|
+
class ItemUpdaterWithDumping
|
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(dump(attributes))
|
14
|
+
end
|
15
|
+
|
16
|
+
def set(attributes)
|
17
|
+
@item_updater.set(dump(attributes))
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def dump(attributes)
|
23
|
+
dumped = {}
|
24
|
+
|
25
|
+
attributes.each do |name, value|
|
26
|
+
dumped[name] = Dumping.dump_field(value, @model_class.attributes[name])
|
27
|
+
end
|
28
|
+
|
29
|
+
dumped
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'item_updater_with_dumping'
|
4
|
+
|
3
5
|
module Dynamoid
|
4
6
|
module Persistence
|
5
7
|
# @private
|
@@ -36,9 +38,10 @@ module Dynamoid
|
|
36
38
|
attributes_to_persist = @model.attributes.slice(*@model.changed.map(&:to_sym))
|
37
39
|
|
38
40
|
Dynamoid.adapter.update_item(@model.class.table_name, @model.hash_key, options_to_update_item) do |t|
|
41
|
+
item_updater = ItemUpdaterWithDumping.new(@model.class, t)
|
42
|
+
|
39
43
|
attributes_to_persist.each do |name, value|
|
40
|
-
|
41
|
-
t.set(name => value_dumped)
|
44
|
+
item_updater.set(name => value)
|
42
45
|
end
|
43
46
|
end
|
44
47
|
end
|
@@ -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
|
@@ -32,10 +34,10 @@ module Dynamoid
|
|
32
34
|
|
33
35
|
def update_item
|
34
36
|
Dynamoid.adapter.update_item(@model_class.table_name, @partition_key, options_to_update_item) do |t|
|
37
|
+
item_updater = ItemUpdaterWithCastingAndDumping.new(@model_class, t)
|
38
|
+
|
35
39
|
@attributes.each do |k, v|
|
36
|
-
|
37
|
-
value_dumped = Dumping.dump_field(value_casted, @model_class.attributes[k])
|
38
|
-
t.set(k => value_dumped)
|
40
|
+
item_updater.set(k => v)
|
39
41
|
end
|
40
42
|
end
|
41
43
|
end
|