dynamoid 3.8.0 → 3.9.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.
@@ -187,10 +187,10 @@ module Dynamoid
187
187
  def first(*args)
188
188
  n = args.first || 1
189
189
 
190
- return self.dup.scan_limit(n).to_a.first(*args) if @query.blank?
190
+ return dup.scan_limit(n).to_a.first(*args) if @query.blank?
191
191
  return super if @key_fields_detector.non_key_present?
192
192
 
193
- self.dup.record_limit(n).to_a.first(*args)
193
+ dup.record_limit(n).to_a.first(*args)
194
194
  end
195
195
 
196
196
  # Returns the last item matching the criteria.
@@ -487,7 +487,7 @@ module Dynamoid
487
487
  def pluck(*args)
488
488
  fields = args.map(&:to_sym)
489
489
 
490
- scope = self.dup
490
+ scope = dup
491
491
  scope.project(*fields)
492
492
 
493
493
  if fields.many?
@@ -525,6 +525,7 @@ module Dynamoid
525
525
  def pages
526
526
  raw_pages.lazy.map do |items, options|
527
527
  models = items.map { |i| source.from_database(i) }
528
+ models.each { |m| m.run_callbacks :find }
528
529
  [models, options]
529
530
  end.each
530
531
  end
@@ -787,9 +788,9 @@ module Dynamoid
787
788
  condition = {}
788
789
  type = @source.inheritance_field
789
790
 
790
- if @source.attributes.key?(type)
791
- class_names = @source.deep_subclasses.map(&:name) << @source.name
792
- condition[:"#{type}.in"] = class_names
791
+ if @source.attributes.key?(type) && !@source.abstract_class?
792
+ sti_names = @source.deep_subclasses.map(&:sti_name) << @source.sti_name
793
+ condition[:"#{type}.in"] = sti_names
793
794
  end
794
795
 
795
796
  condition
@@ -26,20 +26,18 @@ module Dynamoid
26
26
  module ClassMethods
27
27
  def update_fields(*)
28
28
  super.tap do |model|
29
- model.send(:clear_changes_information) if model
29
+ model.clear_changes_information if model
30
30
  end
31
31
  end
32
32
 
33
33
  def upsert(*)
34
34
  super.tap do |model|
35
- model.send(:clear_changes_information) if model
35
+ model.clear_changes_information if model
36
36
  end
37
37
  end
38
38
 
39
39
  def from_database(*)
40
- super.tap do |m|
41
- m.send(:clear_changes_information)
42
- end
40
+ super.tap(&:clear_changes_information)
43
41
  end
44
42
  end
45
43
 
@@ -110,7 +108,7 @@ module Dynamoid
110
108
  #
111
109
  # @return [ActiveSupport::HashWithIndifferentAccess]
112
110
  def changes
113
- ActiveSupport::HashWithIndifferentAccess[changed.map { |attr| [attr, attribute_change(attr)] }]
111
+ ActiveSupport::HashWithIndifferentAccess[changed.map { |name| [name, attribute_change(name)] }]
114
112
  end
115
113
 
116
114
  # Returns a hash of attributes that were changed before the model was saved.
@@ -137,6 +135,25 @@ module Dynamoid
137
135
  @changed_attributes ||= ActiveSupport::HashWithIndifferentAccess.new
138
136
  end
139
137
 
138
+ # Clear all dirty data: current changes and previous changes.
139
+ def clear_changes_information
140
+ @previously_changed = ActiveSupport::HashWithIndifferentAccess.new
141
+ @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
142
+ end
143
+
144
+ # Clears dirty data and moves +changes+ to +previous_changes+.
145
+ def changes_applied
146
+ @previously_changed = changes
147
+ @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
148
+ end
149
+
150
+ # Remove changes information for the provided attributes.
151
+ #
152
+ # @param attributes [Array[String]] - a list of attributes to clear changes for
153
+ def clear_attribute_changes(names)
154
+ attributes_changed_by_setter.except!(*names)
155
+ end
156
+
140
157
  # Handle <tt>*_changed?</tt> for +method_missing+.
141
158
  #
142
159
  # person.attribute_changed?(:name) # => true
@@ -145,14 +162,14 @@ module Dynamoid
145
162
  # person.attribute_changed?(:name, from: 'Alice', to: 'Bod')
146
163
  #
147
164
  # @private
148
- # @param attr [Symbol] attribute name
165
+ # @param name [Symbol] attribute name
149
166
  # @param options [Hash] conditions on +from+ and +to+ value (optional)
150
167
  # @option options [Symbol] :from previous attribute value
151
168
  # @option options [Symbol] :to current attribute value
152
- def attribute_changed?(attr, options = {})
153
- result = changes_include?(attr)
154
- result &&= options[:to] == __send__(attr) if options.key?(:to)
155
- result &&= options[:from] == changed_attributes[attr] if options.key?(:from)
169
+ def attribute_changed?(name, options = {})
170
+ result = changes_include?(name)
171
+ result &&= options[:to] == read_attribute(name) if options.key?(:to)
172
+ result &&= options[:from] == changed_attributes[name] if options.key?(:from)
156
173
  result
157
174
  end
158
175
 
@@ -163,16 +180,16 @@ module Dynamoid
163
180
  # person.attribute_was(:name) # => "Alice"
164
181
  #
165
182
  # @private
166
- # @param attr [Symbol] attribute name
167
- def attribute_was(attr)
168
- attribute_changed?(attr) ? changed_attributes[attr] : __send__(attr)
183
+ # @param name [Symbol] attribute name
184
+ def attribute_was(name)
185
+ attribute_changed?(name) ? changed_attributes[name] : read_attribute(name)
169
186
  end
170
187
 
171
188
  # Restore all previous data of the provided attributes.
172
189
  #
173
190
  # @param attributes [Array[Symbol]] a list of attribute names
174
- def restore_attributes(attributes = changed)
175
- attributes.each { |attr| restore_attribute! attr }
191
+ def restore_attributes(names = changed)
192
+ names.each { |name| restore_attribute! name }
176
193
  end
177
194
 
178
195
  # Handles <tt>*_previously_changed?</tt> for +method_missing+.
@@ -183,10 +200,10 @@ module Dynamoid
183
200
  # person.attribute_changed?(:name) # => true
184
201
  #
185
202
  # @private
186
- # @param attr [Symbol] attribute name
203
+ # @param name [Symbol] attribute name
187
204
  # @return [true|false]
188
- def attribute_previously_changed?(attr)
189
- previous_changes_include?(attr)
205
+ def attribute_previously_changed?(name)
206
+ previous_changes_include?(name)
190
207
  end
191
208
 
192
209
  # Handles <tt>*_previous_change</tt> for +method_missing+.
@@ -197,61 +214,49 @@ module Dynamoid
197
214
  # person.attribute_previously_changed(:name) # => ["Alice", "Bob"]
198
215
  #
199
216
  # @private
200
- # @param attr [Symbol]
217
+ # @param name [Symbol]
201
218
  # @return [Array]
202
- def attribute_previous_change(attr)
203
- previous_changes[attr] if attribute_previously_changed?(attr)
219
+ def attribute_previous_change(name)
220
+ previous_changes[name] if attribute_previously_changed?(name)
204
221
  end
205
222
 
206
223
  private
207
224
 
208
- def changes_include?(attr_name)
209
- attributes_changed_by_setter.include?(attr_name)
225
+ def changes_include?(name)
226
+ attributes_changed_by_setter.include?(name)
210
227
  end
211
228
  alias attribute_changed_by_setter? changes_include?
212
229
 
213
- # Removes current changes and makes them accessible through +previous_changes+.
214
- def changes_applied # :doc:
215
- @previously_changed = changes
216
- @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
217
- end
218
-
219
- # Clear all dirty data: current changes and previous changes.
220
- def clear_changes_information # :doc:
221
- @previously_changed = ActiveSupport::HashWithIndifferentAccess.new
222
- @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
223
- end
224
-
225
230
  # Handle <tt>*_change</tt> for +method_missing+.
226
- def attribute_change(attr)
227
- [changed_attributes[attr], __send__(attr)] if attribute_changed?(attr)
231
+ def attribute_change(name)
232
+ [changed_attributes[name], read_attribute(name)] if attribute_changed?(name)
228
233
  end
229
234
 
230
235
  # Handle <tt>*_will_change!</tt> for +method_missing+.
231
- def attribute_will_change!(attr)
232
- return if attribute_changed?(attr)
236
+ def attribute_will_change!(name)
237
+ return if attribute_changed?(name)
233
238
 
234
239
  begin
235
- value = __send__(attr)
240
+ value = read_attribute(name)
236
241
  value = value.duplicable? ? value.clone : value
237
242
  rescue TypeError, NoMethodError
238
243
  end
239
244
 
240
- set_attribute_was(attr, value)
245
+ set_attribute_was(name, value)
241
246
  end
242
247
 
243
248
  # Handle <tt>restore_*!</tt> for +method_missing+.
244
- def restore_attribute!(attr)
245
- if attribute_changed?(attr)
246
- __send__("#{attr}=", changed_attributes[attr])
247
- clear_attribute_changes([attr])
249
+ def restore_attribute!(name)
250
+ if attribute_changed?(name)
251
+ write_attribute(name, changed_attributes[name])
252
+ clear_attribute_changes([name])
248
253
  end
249
254
  end
250
255
 
251
- # Returns +true+ if attr_name were changed before the model was saved,
256
+ # Returns +true+ if name were changed before the model was saved,
252
257
  # +false+ otherwise.
253
- def previous_changes_include?(attr_name)
254
- previous_changes.include?(attr_name)
258
+ def previous_changes_include?(name)
259
+ previous_changes.include?(name)
255
260
  end
256
261
 
257
262
  # This is necessary because `changed_attributes` might be overridden in
@@ -259,13 +264,8 @@ module Dynamoid
259
264
  alias attributes_changed_by_setter changed_attributes
260
265
 
261
266
  # Force an attribute to have a particular "before" value
262
- def set_attribute_was(attr, old_value)
263
- attributes_changed_by_setter[attr] = old_value
264
- end
265
-
266
- # Remove changes information for the provided attributes.
267
- def clear_attribute_changes(attributes)
268
- attributes_changed_by_setter.except!(*attributes)
267
+ def set_attribute_was(name, old_value)
268
+ attributes_changed_by_setter[name] = old_value
269
269
  end
270
270
  end
271
271
  end
@@ -160,6 +160,22 @@ module Dynamoid
160
160
  end
161
161
  end
162
162
 
163
+ attr_accessor :abstract_class
164
+
165
+ def abstract_class?
166
+ defined?(@abstract_class) && @abstract_class == true
167
+ end
168
+
169
+ def sti_name
170
+ name
171
+ end
172
+
173
+ def sti_class_for(type_name)
174
+ type_name.constantize
175
+ rescue NameError
176
+ raise Errors::SubclassNotFound, "STI subclass does not found. Subclass: '#{type_name}'"
177
+ end
178
+
163
179
  # @private
164
180
  def deep_subclasses
165
181
  subclasses + subclasses.map(&:deep_subclasses).flatten
@@ -167,7 +183,7 @@ module Dynamoid
167
183
 
168
184
  # @private
169
185
  def choose_right_class(attrs)
170
- attrs[inheritance_field] ? attrs[inheritance_field].constantize : self
186
+ attrs[inheritance_field] ? sti_class_for(attrs[inheritance_field]) : self
171
187
  end
172
188
  end
173
189
 
@@ -206,7 +222,7 @@ module Dynamoid
206
222
  load(attrs_with_defaults.merge(attrs_virtual))
207
223
 
208
224
  if block
209
- block.call(self)
225
+ yield(self)
210
226
  end
211
227
  end
212
228
  end
@@ -278,6 +294,26 @@ module Dynamoid
278
294
  end
279
295
  end
280
296
 
297
+ def inspect
298
+ # attributes order is:
299
+ # - partition key
300
+ # - sort key
301
+ # - user defined attributes
302
+ # - timestamps - created_at/updated_at
303
+ names = [self.class.hash_key]
304
+ names << self.class.range_key if self.class.range_key
305
+ names += self.class.attributes.keys - names - %i[created_at updated_at]
306
+ names << :created_at if self.class.attributes.key?(:created_at)
307
+ names << :updated_at if self.class.attributes.key?(:updated_at)
308
+
309
+ inspection = names.map do |name|
310
+ value = read_attribute(name)
311
+ "#{name}: #{value.inspect}"
312
+ end.join(', ')
313
+
314
+ "#<#{self.class.name} #{inspection}>"
315
+ end
316
+
281
317
  private
282
318
 
283
319
  def dumped_range_value
@@ -77,5 +77,7 @@ module Dynamoid
77
77
  class UnsupportedKeyType < Error; end
78
78
 
79
79
  class UnknownAttribute < Error; end
80
+
81
+ class SubclassNotFound < Error; end
80
82
  end
81
83
  end
@@ -293,7 +293,7 @@ module Dynamoid
293
293
  old_value = read_attribute(name)
294
294
 
295
295
  unless attribute_is_present_on_model?(name)
296
- raise Dynamoid::Errors::UnknownAttribute.new("Attribute #{name} is not part of the model")
296
+ raise Dynamoid::Errors::UnknownAttribute, "Attribute #{name} is not part of the model"
297
297
  end
298
298
 
299
299
  if association = @associations[name]
@@ -354,23 +354,6 @@ module Dynamoid
354
354
 
355
355
  private
356
356
 
357
- # Automatically called during the created callback to set the created_at time.
358
- #
359
- # @since 0.2.0
360
- def set_created_at
361
- self.created_at ||= DateTime.now.in_time_zone(Time.zone) if self.class.timestamps_enabled?
362
- end
363
-
364
- # Automatically called during the save callback to set the updated_at time.
365
- #
366
- # @since 0.2.0
367
- def set_updated_at
368
- # @_touch_record=false means explicit disabling
369
- if self.class.timestamps_enabled? && changed? && !updated_at_changed? && @_touch_record != false
370
- self.updated_at = DateTime.now.in_time_zone(Time.zone)
371
- end
372
- end
373
-
374
357
  def set_expires_field
375
358
  options = self.class.options[:expires]
376
359
 
@@ -386,11 +369,12 @@ module Dynamoid
386
369
 
387
370
  def set_inheritance_field
388
371
  # actually it does only following logic:
389
- # self.type ||= self.class.name if self.class.attributes[:type]
372
+ # self.type ||= self.class.sti_name if self.class.attributes[:type]
373
+ return if self.class.abstract_class?
390
374
 
391
375
  type = self.class.inheritance_field
392
376
  if self.class.attributes[type] && send(type).nil?
393
- send("#{type}=", self.class.name)
377
+ send("#{type}=", self.class.sti_name)
394
378
  end
395
379
  end
396
380
 
@@ -118,7 +118,7 @@ module Dynamoid
118
118
 
119
119
  # @private
120
120
  def _find_all(ids, options = {})
121
- raise Errors::MissingRangeKey if range_key && ids.any? { |pk, sk| sk.nil? }
121
+ raise Errors::MissingRangeKey if range_key && ids.any? { |_pk, sk| sk.nil? }
122
122
 
123
123
  if range_key
124
124
  ids = ids.map do |pk, sk|
@@ -151,7 +151,9 @@ module Dynamoid
151
151
  end
152
152
 
153
153
  if items.size == ids.size || !options[:raise_error]
154
- items ? items.map { |i| from_database(i) } : []
154
+ models = items ? items.map { |i| from_database(i) } : []
155
+ models.each { |m| m.run_callbacks :find }
156
+ models
155
157
  else
156
158
  ids_list = range_key ? ids.map { |pk, sk| "(#{pk},#{sk})" } : ids.map(&:to_s)
157
159
  message = "Couldn't find all #{name.pluralize} with primary keys [#{ids_list.join(', ')}] "
@@ -173,7 +175,9 @@ module Dynamoid
173
175
  end
174
176
 
175
177
  if item = Dynamoid.adapter.read(table_name, id, options.slice(:range_key, :consistent_read))
176
- from_database(item)
178
+ model = from_database(item)
179
+ model.run_callbacks :find
180
+ model
177
181
  elsif options[:raise_error]
178
182
  primary_key = range_key ? "(#{id},#{options[:range_key]})" : id
179
183
  message = "Couldn't find #{name} with primary key #{primary_key}"
@@ -294,7 +298,8 @@ module Dynamoid
294
298
  # @private
295
299
  # @since 0.2.0
296
300
  def method_missing(method, *args)
297
- if method =~ /find/
301
+ # Cannot use Symbol#start_with? because it was introduced in Ruby 2.7, but we support Ruby >= 2.3
302
+ if method.to_s.start_with?('find')
298
303
  ActiveSupport::Deprecation.warn("[Dynamoid] .#{method} is deprecated! Call .where instead of")
299
304
 
300
305
  finder = method.to_s.split('_by_').first
@@ -169,10 +169,9 @@ module Dynamoid
169
169
  # @return [Dynamoid::Indexes::Index, nil] index object or nil if it isn't found
170
170
  def find_index_by_name(name)
171
171
  string_name = name.to_s
172
- indexes.each_value.detect{ |i| i.name.to_s == string_name }
172
+ indexes.each_value.detect { |i| i.name.to_s == string_name }
173
173
  end
174
174
 
175
-
176
175
  # Returns true iff the provided hash[,range] key combo is a local
177
176
  # secondary index.
178
177
  #
@@ -299,7 +298,6 @@ module Dynamoid
299
298
  end
300
299
  end
301
300
 
302
-
303
301
  def validate_hash_key
304
302
  validate_index_key(:hash_key, @hash_key)
305
303
  end
@@ -319,7 +317,7 @@ module Dynamoid
319
317
 
320
318
  key_dynamodb_type = dynamodb_type(key_field_attributes[:type], key_field_attributes)
321
319
  if PERMITTED_KEY_DYNAMODB_TYPES.include?(key_dynamodb_type)
322
- self.send("#{key_param}_schema=", { key_val => key_dynamodb_type })
320
+ send("#{key_param}_schema=", { key_val => key_dynamodb_type })
323
321
  else
324
322
  errors.add(key_param, "Index :#{key_param} is not a valid key type")
325
323
  end
@@ -1,10 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Dynamoid
2
4
  module Log
5
+ # https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/Log/Formatter.html
6
+ # https://docs.aws.amazon.com/sdk-for-ruby/v2/api/Seahorse/Client/Response.html
7
+ # https://aws.amazon.com/ru/blogs/developer/logging-requests/
3
8
  module Formatter
4
-
5
- # https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/Log/Formatter.html
6
- # https://docs.aws.amazon.com/sdk-for-ruby/v2/api/Seahorse/Client/Response.html
7
- # https://aws.amazon.com/ru/blogs/developer/logging-requests/
8
9
  class Debug
9
10
  def format(response)
10
11
  bold = "\x1b[1m"
@@ -21,6 +22,20 @@ module Dynamoid
21
22
  ].join("\n")
22
23
  end
23
24
  end
25
+
26
+ class Compact
27
+ def format(response)
28
+ bold = "\x1b[1m"
29
+ reset = "\x1b[0m"
30
+
31
+ [
32
+ response.context.operation.name,
33
+ bold,
34
+ response.context.http_request.body.string,
35
+ reset
36
+ ].join(' ')
37
+ end
38
+ end
24
39
  end
25
40
  end
26
41
  end
@@ -24,7 +24,10 @@ module Dynamoid
24
24
  import_with_backoff(models)
25
25
  end
26
26
 
27
- models.each { |d| d.new_record = false }
27
+ models.each do |m|
28
+ m.new_record = false
29
+ m.clear_changes_information
30
+ end
28
31
  models
29
32
  end
30
33
 
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dynamoid
4
+ module Persistence
5
+ # @private
6
+ class Inc
7
+ def self.call(model_class, hash_key, range_key = nil, counters)
8
+ new(model_class, hash_key, range_key, counters).call
9
+ end
10
+
11
+ # rubocop:disable Style/OptionalArguments
12
+ def initialize(model_class, hash_key, range_key = nil, counters)
13
+ @model_class = model_class
14
+ @hash_key = hash_key
15
+ @range_key = range_key
16
+ @counters = counters
17
+ end
18
+ # rubocop:enable Style/OptionalArguments
19
+
20
+ def call
21
+ touch = @counters.delete(:touch)
22
+
23
+ Dynamoid.adapter.update_item(@model_class.table_name, @hash_key, update_item_options) do |t|
24
+ @counters.each do |name, value|
25
+ t.add(name => cast_and_dump_attribute_value(name, value))
26
+ end
27
+
28
+ if touch
29
+ value = DateTime.now.in_time_zone(Time.zone)
30
+
31
+ timestamp_attributes_to_touch(touch).each do |name|
32
+ t.set(name => cast_and_dump_attribute_value(name, value))
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def update_item_options
41
+ if @model_class.range_key
42
+ range_key_options = @model_class.attributes[@model_class.range_key]
43
+ value_casted = TypeCasting.cast_field(@range_key, range_key_options)
44
+ value_dumped = Dumping.dump_field(value_casted, range_key_options)
45
+ { range_key: value_dumped }
46
+ else
47
+ {}
48
+ end
49
+ end
50
+
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
+ def timestamp_attributes_to_touch(touch)
57
+ return [] unless touch
58
+
59
+ names = []
60
+ names << :updated_at if @model_class.timestamps_enabled?
61
+ names += Array.wrap(touch) if touch != true
62
+ names
63
+ end
64
+ end
65
+ end
66
+ end
@@ -4,24 +4,44 @@ module Dynamoid
4
4
  module Persistence
5
5
  # @private
6
6
  class Save
7
- def self.call(model)
8
- new(model).call
7
+ def self.call(model, **options)
8
+ new(model, **options).call
9
9
  end
10
10
 
11
- def initialize(model)
11
+ def initialize(model, touch: nil)
12
12
  @model = model
13
+ @touch = touch # touch=false means explicit disabling of updating the `updated_at` attribute
13
14
  end
14
15
 
15
16
  def call
16
17
  @model.hash_key = SecureRandom.uuid if @model.hash_key.blank?
17
18
 
19
+ return true unless @model.changed?
20
+
21
+ @model.created_at ||= DateTime.now.in_time_zone(Time.zone) if @model.class.timestamps_enabled?
22
+
23
+ if @model.class.timestamps_enabled? && !@model.updated_at_changed? && !(@touch == false && @model.persisted?)
24
+ @model.updated_at = DateTime.now.in_time_zone(Time.zone)
25
+ end
26
+
18
27
  # Add an optimistic locking check if the lock_version column exists
19
28
  if @model.class.attributes[:lock_version]
20
29
  @model.lock_version = (@model.lock_version || 0) + 1
21
30
  end
22
31
 
23
- attributes_dumped = Dumping.dump_attributes(@model.attributes, @model.class.attributes)
24
- Dynamoid.adapter.write(@model.class.table_name, attributes_dumped, conditions_for_write)
32
+ if @model.new_record?
33
+ attributes_dumped = Dumping.dump_attributes(@model.attributes, @model.class.attributes)
34
+ Dynamoid.adapter.write(@model.class.table_name, attributes_dumped, conditions_for_write)
35
+ else
36
+ attributes_to_persist = @model.attributes.slice(*@model.changed.map(&:to_sym))
37
+
38
+ Dynamoid.adapter.update_item(@model.class.table_name, @model.hash_key, options_to_update_item) do |t|
39
+ attributes_to_persist.each do |name, value|
40
+ value_dumped = Dumping.dump_field(value, @model.class.attributes[name])
41
+ t.set(name => value_dumped)
42
+ end
43
+ end
44
+ end
25
45
 
26
46
  @model.new_record = false
27
47
  true
@@ -59,6 +79,33 @@ module Dynamoid
59
79
 
60
80
  conditions
61
81
  end
82
+
83
+ def options_to_update_item
84
+ options = {}
85
+
86
+ if @model.class.range_key
87
+ value_dumped = Dumping.dump_field(@model.range_value, @model.class.attributes[@model.class.range_key])
88
+ options[:range_key] = value_dumped
89
+ end
90
+
91
+ conditions = {}
92
+ conditions[:if_exists] ||= {}
93
+ conditions[:if_exists][@model.class.hash_key] = @model.hash_key
94
+
95
+ # Add an optimistic locking check if the lock_version column exists
96
+ if @model.class.attributes[:lock_version]
97
+ # Uses the original lock_version value from Dirty API
98
+ # in case user changed 'lock_version' manually
99
+ if @model.changes[:lock_version][0]
100
+ conditions[:if] ||= {}
101
+ conditions[:if][:lock_version] = @model.changes[:lock_version][0]
102
+ end
103
+ end
104
+
105
+ options[:conditions] = conditions
106
+
107
+ options
108
+ end
62
109
  end
63
110
  end
64
111
  end
@@ -9,7 +9,7 @@ module Dynamoid
9
9
 
10
10
  attributes.each do |attr_name, _|
11
11
  unless model_attributes.include?(attr_name)
12
- raise Dynamoid::Errors::UnknownAttribute.new("Attribute #{attr_name} does not exist in #{model_class}")
12
+ raise Dynamoid::Errors::UnknownAttribute, "Attribute #{attr_name} does not exist in #{model_class}"
13
13
  end
14
14
  end
15
15
  end