dynamoid 3.11.0 → 3.13.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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +39 -3
  3. data/README.md +94 -14
  4. data/SECURITY.md +6 -6
  5. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/batch_get_item.rb +3 -1
  6. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/item_updater.rb +1 -1
  7. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/query.rb +4 -1
  8. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/scan.rb +4 -1
  9. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/table.rb +13 -0
  10. data/lib/dynamoid/adapter_plugin/aws_sdk_v3.rb +24 -9
  11. data/lib/dynamoid/config.rb +1 -0
  12. data/lib/dynamoid/criteria/chain.rb +11 -3
  13. data/lib/dynamoid/dirty.rb +22 -11
  14. data/lib/dynamoid/dumping.rb +3 -3
  15. data/lib/dynamoid/errors.rb +16 -1
  16. data/lib/dynamoid/fields/declare.rb +1 -1
  17. data/lib/dynamoid/fields.rb +44 -4
  18. data/lib/dynamoid/finders.rb +44 -19
  19. data/lib/dynamoid/persistence/inc.rb +30 -13
  20. data/lib/dynamoid/persistence/save.rb +24 -12
  21. data/lib/dynamoid/persistence/update_fields.rb +18 -5
  22. data/lib/dynamoid/persistence/update_validations.rb +3 -3
  23. data/lib/dynamoid/persistence/upsert.rb +17 -4
  24. data/lib/dynamoid/persistence.rb +273 -19
  25. data/lib/dynamoid/transaction_read/find.rb +137 -0
  26. data/lib/dynamoid/transaction_read.rb +146 -0
  27. data/lib/dynamoid/transaction_write/base.rb +12 -0
  28. data/lib/dynamoid/transaction_write/delete_with_instance.rb +7 -2
  29. data/lib/dynamoid/transaction_write/delete_with_primary_key.rb +7 -2
  30. data/lib/dynamoid/transaction_write/destroy.rb +10 -5
  31. data/lib/dynamoid/transaction_write/item_updater.rb +60 -0
  32. data/lib/dynamoid/transaction_write/save.rb +22 -9
  33. data/lib/dynamoid/transaction_write/update_fields.rb +176 -31
  34. data/lib/dynamoid/transaction_write/upsert.rb +23 -6
  35. data/lib/dynamoid/transaction_write.rb +212 -3
  36. data/lib/dynamoid/validations.rb +15 -4
  37. data/lib/dynamoid/version.rb +1 -1
  38. data/lib/dynamoid.rb +1 -0
  39. metadata +9 -9
@@ -134,6 +134,11 @@ module Dynamoid
134
134
  # @param name [Symbol] name of the field
135
135
  # @param type [Symbol] type of the field (optional)
136
136
  # @param options [Hash] any additional options for the field type (optional)
137
+ # @option options [Symbol] :of <description>
138
+ # @option options [Symbol] :store_as_string <description>
139
+ # @option options [Symbol] :store_as_native_boolean <description>
140
+ # @option options [Symbol] :default <description>
141
+ # @option options [Symbol] :alias <description>
137
142
  #
138
143
  # @since 0.2.0
139
144
  def field(name, type = :string, options = {})
@@ -197,10 +202,22 @@ module Dynamoid
197
202
  # field :id, :integer
198
203
  # end
199
204
  #
205
+ # To declare a new attribute with not-default type as a table hash key a
206
+ # :key_type option can be used:
207
+ #
208
+ # class User
209
+ # include Dynamoid::Document
210
+ #
211
+ # table key: :user_id, key_type: :integer
212
+ # end
213
+ #
200
214
  # @param options [Hash] options to override default table settings
201
215
  # @option options [Symbol] :name name of a table
216
+ # @option options [Symbol] :arn table ARN; it allows referring tables in another AWS accounts; has higher priority than the +name+ option
202
217
  # @option options [Symbol] :key name of a hash key attribute
218
+ # @option options [Symbol] :key_type type of a hash key attribute
203
219
  # @option options [Symbol] :inheritance_field name of an attribute used for STI
220
+ # @option options [Array<Symbol>] :skip_generating_fields don't generate implicitly methods with given names, e.g. +:id+, +:created_at+, +:updated_at+
204
221
  # @option options [Symbol] :capacity_mode table billing mode - either +provisioned+ or +on_demand+
205
222
  # @option options [Integer] :write_capacity table write capacity units
206
223
  # @option options [Integer] :read_capacity table read capacity units
@@ -211,10 +228,17 @@ module Dynamoid
211
228
  def table(options)
212
229
  self.options = options
213
230
 
231
+ id_already_declared = true
232
+ timestamps_already_declared = Dynamoid::Config.timestamps
233
+
214
234
  # a default 'id' column is created when Dynamoid::Document is included
215
235
  unless attributes.key? hash_key
216
236
  remove_field :id
217
- field(hash_key)
237
+ id_already_declared = false
238
+
239
+ key_type = options[:key_type] || :string
240
+ field(hash_key, key_type)
241
+ id_already_declared = hash_key == :id
218
242
  end
219
243
 
220
244
  # The created_at/updated_at fields are declared in the `included` callback first.
@@ -223,14 +247,30 @@ module Dynamoid
223
247
  # So we need to make decision again and declare the fields or rollback thier declaration.
224
248
  #
225
249
  # Do not replace with `#timestamps_enabled?`.
226
- if options[:timestamps] && !Dynamoid::Config.timestamps
250
+ if options[:timestamps] && !timestamps_already_declared
227
251
  # The fields weren't declared in `included` callback because they are disabled globaly
228
252
  field :created_at, :datetime
229
253
  field :updated_at, :datetime
230
- elsif options[:timestamps] == false && Dynamoid::Config.timestamps
254
+
255
+ timestamps_already_declared = true
256
+ elsif options[:timestamps] == false && timestamps_already_declared
231
257
  # The fields were declared in `included` callback but they are disabled for a table
232
258
  remove_field :created_at
233
259
  remove_field :updated_at
260
+
261
+ timestamps_already_declared = false
262
+ end
263
+
264
+ # handle :skip_generating_fields option
265
+ skip_generating_fields = options[:skip_generating_fields] || []
266
+ if id_already_declared && skip_generating_fields.include?(:id)
267
+ remove_field :id
268
+ end
269
+ if timestamps_already_declared && skip_generating_fields.include?(:created_at)
270
+ remove_field :created_at
271
+ end
272
+ if timestamps_already_declared && skip_generating_fields.include?(:updated_at)
273
+ remove_field :updated_at
234
274
  end
235
275
  end
236
276
 
@@ -291,7 +331,7 @@ module Dynamoid
291
331
  old_value = read_attribute(name)
292
332
 
293
333
  unless attribute_is_present_on_model?(name)
294
- raise Dynamoid::Errors::UnknownAttribute, "Attribute #{name} is not part of the model"
334
+ raise Dynamoid::Errors::UnknownAttribute.new(self.class, name)
295
335
  end
296
336
 
297
337
  if association = @associations[name]
@@ -14,12 +14,12 @@ module Dynamoid
14
14
  # specified +raise_error: false+ option then +find+ will not raise the
15
15
  # exception.
16
16
  #
17
- # When a document schema includes range key it always should be specified
17
+ # When a document schema includes range key it should always be specified
18
18
  # in +find+ method call. In case it's missing +MissingRangeKey+ exception
19
19
  # will be raised.
20
20
  #
21
21
  # Please note that +find+ doesn't preserve order of models in result when
22
- # passes multiple ids.
22
+ # given multiple ids.
23
23
  #
24
24
  # Supported following options:
25
25
  # * +consistent_read+
@@ -107,24 +107,40 @@ module Dynamoid
107
107
 
108
108
  # @private
109
109
  def _find_all(ids, options = {})
110
- raise Errors::MissingRangeKey if range_key && ids.any? { |_pk, sk| sk.nil? }
111
-
112
- if range_key
113
- ids = ids.map do |pk, sk|
114
- sk_casted = TypeCasting.cast_field(sk, attributes[range_key])
115
- sk_dumped = Dumping.dump_field(sk_casted, attributes[range_key])
110
+ ids = ids.map do |id|
111
+ if range_key
112
+ # expect [hash key, range key] pair
113
+ pk, sk = id
114
+
115
+ if pk.nil?
116
+ raise Errors::MissingHashKey
117
+ end
118
+ if sk.nil?
119
+ raise Errors::MissingRangeKey
120
+ end
121
+
122
+ pk_dumped = cast_and_dump(hash_key, pk)
123
+ sk_dumped = cast_and_dump(range_key, sk)
124
+
125
+ [pk_dumped, sk_dumped]
126
+ else
127
+ if id.nil?
128
+ raise Errors::MissingHashKey
129
+ end
116
130
 
117
- [pk, sk_dumped]
131
+ cast_and_dump(hash_key, id)
118
132
  end
119
133
  end
120
134
 
121
135
  read_options = options.slice(:consistent_read)
136
+ table = Dynamoid.adapter.describe_table(table_name)
122
137
 
123
138
  items = if Dynamoid.config.backoff
124
139
  items = []
125
140
  backoff = nil
126
141
  Dynamoid.adapter.read(table_name, ids, read_options) do |hash, has_unprocessed_items|
127
- items += hash[table_name]
142
+ # Cannot use #table_name because it may contain a table ARN, but response contains always a table name
143
+ items += hash[table.name]
128
144
 
129
145
  if has_unprocessed_items
130
146
  backoff ||= Dynamoid.config.build_backoff
@@ -136,7 +152,9 @@ module Dynamoid
136
152
  items
137
153
  else
138
154
  items = Dynamoid.adapter.read(table_name, ids, read_options)
139
- items ? items[table_name] : []
155
+
156
+ # Cannot use #table_name because it may contain a table ARN, but response contains always a table name
157
+ items ? items[table.name] : []
140
158
  end
141
159
 
142
160
  if items.size == ids.size || !options[:raise_error]
@@ -144,7 +162,7 @@ module Dynamoid
144
162
  models.each { |m| m.run_callbacks :find }
145
163
  models
146
164
  else
147
- ids_list = range_key ? ids.map { |pk, sk| "(#{pk},#{sk})" } : ids.map(&:to_s)
165
+ ids_list = range_key ? ids.map { |pk, sk| "(#{pk.inspect},#{sk.inspect})" } : ids.map(&:inspect)
148
166
  message = "Couldn't find all #{name.pluralize} with primary keys [#{ids_list.join(', ')}] "
149
167
  message += "(found #{items.size} results, but was looking for #{ids.size})"
150
168
  raise Errors::RecordNotFound, message
@@ -153,22 +171,21 @@ module Dynamoid
153
171
 
154
172
  # @private
155
173
  def _find_by_id(id, options = {})
174
+ raise Errors::MissingHashKey if id.nil?
156
175
  raise Errors::MissingRangeKey if range_key && options[:range_key].nil?
157
176
 
158
- if range_key
159
- key = options[:range_key]
160
- key_casted = TypeCasting.cast_field(key, attributes[range_key])
161
- key_dumped = Dumping.dump_field(key_casted, attributes[range_key])
177
+ partition_key_dumped = cast_and_dump(hash_key, id)
162
178
 
163
- options[:range_key] = key_dumped
179
+ if range_key
180
+ options[:range_key] = cast_and_dump(range_key, options[:range_key])
164
181
  end
165
182
 
166
- if item = Dynamoid.adapter.read(table_name, id, options.slice(:range_key, :consistent_read))
183
+ if item = Dynamoid.adapter.read(table_name, partition_key_dumped, options.slice(:range_key, :consistent_read))
167
184
  model = from_database(item)
168
185
  model.run_callbacks :find
169
186
  model
170
187
  elsif options[:raise_error]
171
- primary_key = range_key ? "(#{id},#{options[:range_key]})" : id
188
+ primary_key = range_key ? "(#{id.inspect},#{options[:range_key].inspect})" : id.inspect
172
189
  message = "Couldn't find #{name} with primary key #{primary_key}"
173
190
  raise Errors::RecordNotFound, message
174
191
  end
@@ -308,6 +325,14 @@ module Dynamoid
308
325
  super
309
326
  end
310
327
  end
328
+
329
+ private
330
+
331
+ def cast_and_dump(name, value)
332
+ attribute_options = attributes[name]
333
+ casted_value = TypeCasting.cast_field(value, attribute_options)
334
+ Dumping.dump_field(casted_value, attribute_options)
335
+ end
311
336
  end
312
337
  end
313
338
  end
@@ -6,23 +6,25 @@ module Dynamoid
6
6
  module Persistence
7
7
  # @private
8
8
  class Inc
9
- def self.call(model_class, hash_key, range_key = nil, counters)
10
- new(model_class, hash_key, range_key, counters).call
9
+ def self.call(model_class, partition_key, sort_key = nil, counters)
10
+ new(model_class, partition_key, sort_key, counters).call
11
11
  end
12
12
 
13
13
  # rubocop:disable Style/OptionalArguments
14
- def initialize(model_class, hash_key, range_key = nil, counters)
14
+ def initialize(model_class, partition_key, sort_key = nil, counters)
15
15
  @model_class = model_class
16
- @hash_key = hash_key
17
- @range_key = range_key
16
+ @partition_key = partition_key
17
+ @sort_key = sort_key
18
18
  @counters = counters
19
19
  end
20
20
  # rubocop:enable Style/OptionalArguments
21
21
 
22
22
  def call
23
23
  touch = @counters.delete(:touch)
24
+ partition_key_dumped = cast_and_dump(@model_class.hash_key, @partition_key)
25
+ options = update_item_options(partition_key_dumped)
24
26
 
25
- Dynamoid.adapter.update_item(@model_class.table_name, @hash_key, update_item_options) do |t|
27
+ Dynamoid.adapter.update_item(@model_class.table_name, partition_key_dumped, options) do |t|
26
28
  item_updater = ItemUpdaterWithCastingAndDumping.new(@model_class, t)
27
29
 
28
30
  @counters.each do |name, value|
@@ -37,19 +39,28 @@ module Dynamoid
37
39
  end
38
40
  end
39
41
  end
42
+ rescue Dynamoid::Errors::ConditionalCheckFailedException # rubocop:disable Lint/SuppressedException
40
43
  end
41
44
 
42
45
  private
43
46
 
44
- def update_item_options
47
+ def update_item_options(partition_key_dumped)
48
+ options = {}
49
+
50
+ conditions = {
51
+ if: {
52
+ @model_class.hash_key => partition_key_dumped
53
+ }
54
+ }
55
+ options[:conditions] = conditions
56
+
45
57
  if @model_class.range_key
46
- range_key_options = @model_class.attributes[@model_class.range_key]
47
- value_casted = TypeCasting.cast_field(@range_key, range_key_options)
48
- value_dumped = Dumping.dump_field(value_casted, range_key_options)
49
- { range_key: value_dumped }
50
- else
51
- {}
58
+ sort_key_dumped = cast_and_dump(@model_class.range_key, @sort_key)
59
+ options[:range_key] = sort_key_dumped
60
+ options[:conditions][@model_class.range_key] = sort_key_dumped
52
61
  end
62
+
63
+ options
53
64
  end
54
65
 
55
66
  def timestamp_attributes_to_touch(touch)
@@ -60,6 +71,12 @@ module Dynamoid
60
71
  names += Array.wrap(touch) if touch != true
61
72
  names
62
73
  end
74
+
75
+ def cast_and_dump(name, value)
76
+ options = @model_class.attributes[name]
77
+ value_casted = TypeCasting.cast_field(value, options)
78
+ Dumping.dump_field(value_casted, options)
79
+ end
63
80
  end
64
81
  end
65
82
  end
@@ -12,10 +12,12 @@ module Dynamoid
12
12
 
13
13
  def initialize(model, touch: nil)
14
14
  @model = model
15
- @touch = touch # touch=false means explicit disabling of updating the `updated_at` attribute
15
+ @touch = touch # `touch: false` means explicit disabling of updating the `updated_at` attribute
16
16
  end
17
17
 
18
18
  def call
19
+ validate_primary_key!
20
+
19
21
  @model.hash_key = SecureRandom.uuid if @model.hash_key.blank?
20
22
 
21
23
  return true unless @model.changed?
@@ -36,8 +38,10 @@ module Dynamoid
36
38
  Dynamoid.adapter.write(@model.class.table_name, attributes_dumped, conditions_for_write)
37
39
  else
38
40
  attributes_to_persist = @model.attributes.slice(*@model.changed.map(&:to_sym))
41
+ partition_key_dumped = dump(@model.class.hash_key, @model.hash_key)
42
+ options = options_to_update_item(partition_key_dumped)
39
43
 
40
- Dynamoid.adapter.update_item(@model.class.table_name, @model.hash_key, options_to_update_item) do |t|
44
+ Dynamoid.adapter.update_item(@model.class.table_name, partition_key_dumped, options) do |t|
41
45
  item_updater = ItemUpdaterWithDumping.new(@model.class, t)
42
46
 
43
47
  attributes_to_persist.each do |name, value|
@@ -58,22 +62,25 @@ module Dynamoid
58
62
 
59
63
  private
60
64
 
65
+ def validate_primary_key!
66
+ raise Dynamoid::Errors::MissingHashKey if !@model.new_record? && @model.hash_key.nil?
67
+ raise Dynamoid::Errors::MissingRangeKey if @model.class.range_key? && @model.range_value.nil?
68
+ end
69
+
61
70
  # Should be called after incrementing `lock_version` attribute
62
71
  def conditions_for_write
63
72
  conditions = {}
64
73
 
65
74
  # Add an 'exists' check to prevent overwriting existing records with new ones
66
- if @model.new_record?
67
- conditions[:unless_exists] = [@model.class.hash_key]
68
- if @model.range_key
69
- conditions[:unless_exists] << @model.range_key
70
- end
75
+ conditions[:unless_exists] = [@model.class.hash_key]
76
+ if @model.range_key
77
+ conditions[:unless_exists] << @model.range_key
71
78
  end
72
79
 
73
80
  # Add an optimistic locking check if the lock_version column exists
74
81
  # Uses the original lock_version value from Dirty API
75
82
  # in case user changed 'lock_version' manually
76
- if @model.class.attributes[:lock_version] && (@model.changes[:lock_version][0])
83
+ if @model.class.attributes[:lock_version] && @model.changes[:lock_version][0]
77
84
  conditions[:if] ||= {}
78
85
  conditions[:if][:lock_version] = @model.changes[:lock_version][0]
79
86
  end
@@ -81,22 +88,22 @@ module Dynamoid
81
88
  conditions
82
89
  end
83
90
 
84
- def options_to_update_item
91
+ def options_to_update_item(partition_key_dumped)
85
92
  options = {}
86
93
 
87
94
  if @model.class.range_key
88
- value_dumped = Dumping.dump_field(@model.range_value, @model.class.attributes[@model.class.range_key])
95
+ value_dumped = dump(@model.class.range_key, @model.range_value)
89
96
  options[:range_key] = value_dumped
90
97
  end
91
98
 
92
99
  conditions = {}
93
100
  conditions[:if] ||= {}
94
- conditions[:if][@model.class.hash_key] = @model.hash_key
101
+ conditions[:if][@model.class.hash_key] = partition_key_dumped
95
102
 
96
103
  # Add an optimistic locking check if the lock_version column exists
97
104
  # Uses the original lock_version value from Dirty API
98
105
  # in case user changed 'lock_version' manually
99
- if @model.class.attributes[:lock_version] && (@model.changes[:lock_version][0])
106
+ if @model.class.attributes[:lock_version] && @model.changes[:lock_version][0]
100
107
  conditions[:if] ||= {}
101
108
  conditions[:if][:lock_version] = @model.changes[:lock_version][0]
102
109
  end
@@ -105,6 +112,11 @@ module Dynamoid
105
112
 
106
113
  options
107
114
  end
115
+
116
+ def dump(name, value)
117
+ options = @model.class.attributes[name]
118
+ Dumping.dump_field(value, options)
119
+ end
108
120
  end
109
121
  end
110
122
  end
@@ -16,9 +16,12 @@ module Dynamoid
16
16
  @sort_key = sort_key
17
17
  @attributes = attributes.symbolize_keys
18
18
  @conditions = conditions
19
+
20
+ @partition_key_dumped = cast_and_dump(@model_class.hash_key, @partition_key)
19
21
  end
20
22
 
21
23
  def call
24
+ validate_primary_key!
22
25
  UpdateValidations.validate_attributes_exist(@model_class, @attributes)
23
26
 
24
27
  if @model_class.timestamps_enabled?
@@ -32,8 +35,13 @@ module Dynamoid
32
35
 
33
36
  private
34
37
 
38
+ def validate_primary_key!
39
+ raise Dynamoid::Errors::MissingHashKey if @partition_key.nil?
40
+ raise Dynamoid::Errors::MissingRangeKey if @model_class.range_key? && @sort_key.nil?
41
+ end
42
+
35
43
  def update_item
36
- Dynamoid.adapter.update_item(@model_class.table_name, @partition_key, options_to_update_item) do |t|
44
+ Dynamoid.adapter.update_item(@model_class.table_name, @partition_key_dumped, options_to_update_item) do |t|
37
45
  item_updater = ItemUpdaterWithCastingAndDumping.new(@model_class, t)
38
46
 
39
47
  @attributes.each do |k, v|
@@ -50,18 +58,23 @@ module Dynamoid
50
58
  options = {}
51
59
 
52
60
  if @model_class.range_key
53
- value_casted = TypeCasting.cast_field(@sort_key, @model_class.attributes[@model_class.range_key])
54
- value_dumped = Dumping.dump_field(value_casted, @model_class.attributes[@model_class.range_key])
55
- options[:range_key] = value_dumped
61
+ range_key_dumped = cast_and_dump(@model_class.range_key, @sort_key)
62
+ options[:range_key] = range_key_dumped
56
63
  end
57
64
 
58
65
  conditions = @conditions.deep_dup
59
66
  conditions[:if] ||= {}
60
- conditions[:if][@model_class.hash_key] = @partition_key
67
+ conditions[:if][@model_class.hash_key] = @partition_key_dumped
61
68
  options[:conditions] = conditions
62
69
 
63
70
  options
64
71
  end
72
+
73
+ def cast_and_dump(name, value)
74
+ options = @model_class.attributes[name]
75
+ value_casted = TypeCasting.cast_field(value, options)
76
+ Dumping.dump_field(value_casted, options)
77
+ end
65
78
  end
66
79
  end
67
80
  end
@@ -7,9 +7,9 @@ module Dynamoid
7
7
  def self.validate_attributes_exist(model_class, attributes)
8
8
  model_attributes = model_class.attributes.keys
9
9
 
10
- attributes.each_key do |attr_name|
11
- unless model_attributes.include?(attr_name)
12
- raise Dynamoid::Errors::UnknownAttribute, "Attribute #{attr_name} does not exist in #{model_class}"
10
+ attributes.each_key do |name|
11
+ unless model_attributes.include?(name)
12
+ raise Dynamoid::Errors::UnknownAttribute.new(model_class, name)
13
13
  end
14
14
  end
15
15
  end
@@ -19,6 +19,7 @@ module Dynamoid
19
19
  end
20
20
 
21
21
  def call
22
+ validate_primary_key!
22
23
  UpdateValidations.validate_attributes_exist(@model_class, @attributes)
23
24
 
24
25
  if @model_class.timestamps_enabled?
@@ -32,8 +33,15 @@ module Dynamoid
32
33
 
33
34
  private
34
35
 
36
+ def validate_primary_key!
37
+ raise Dynamoid::Errors::MissingHashKey if @partition_key.nil?
38
+ raise Dynamoid::Errors::MissingRangeKey if @model_class.range_key? && @sort_key.nil?
39
+ end
40
+
35
41
  def update_item
36
- Dynamoid.adapter.update_item(@model_class.table_name, @partition_key, options_to_update_item) do |t|
42
+ partition_key_dumped = cast_and_dump(@model_class.hash_key, @partition_key)
43
+
44
+ Dynamoid.adapter.update_item(@model_class.table_name, partition_key_dumped, options_to_update_item) do |t|
37
45
  item_updater = ItemUpdaterWithCastingAndDumping.new(@model_class, t)
38
46
 
39
47
  @attributes.each do |k, v|
@@ -46,9 +54,8 @@ module Dynamoid
46
54
  options = {}
47
55
 
48
56
  if @model_class.range_key
49
- value_casted = TypeCasting.cast_field(@sort_key, @model_class.attributes[@model_class.range_key])
50
- value_dumped = Dumping.dump_field(value_casted, @model_class.attributes[@model_class.range_key])
51
- options[:range_key] = value_dumped
57
+ range_key_dumped = cast_and_dump(@model_class.range_key, @sort_key)
58
+ options[:range_key] = range_key_dumped
52
59
  end
53
60
 
54
61
  options[:conditions] = @conditions
@@ -58,6 +65,12 @@ module Dynamoid
58
65
  def undump_attributes(raw_attributes)
59
66
  Undumping.undump_attributes(raw_attributes, @model_class.attributes)
60
67
  end
68
+
69
+ def cast_and_dump(name, value)
70
+ options = @model_class.attributes[name]
71
+ value_casted = TypeCasting.cast_field(value, options)
72
+ Dumping.dump_field(value_casted, options)
73
+ end
61
74
  end
62
75
  end
63
76
  end