dynamoid 3.10.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 +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
|