dynamoid 3.8.0 → 3.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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