dynamoid 3.7.1 → 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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +246 -244
  3. data/LICENSE.txt +2 -2
  4. data/README.md +134 -55
  5. data/SECURITY.md +17 -0
  6. data/dynamoid.gemspec +66 -0
  7. data/lib/dynamoid/adapter.rb +7 -9
  8. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/batch_get_item.rb +2 -2
  9. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/execute_statement.rb +62 -0
  10. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/item_updater.rb +29 -15
  11. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/middleware/limit.rb +3 -0
  12. data/lib/dynamoid/adapter_plugin/aws_sdk_v3.rb +73 -59
  13. data/lib/dynamoid/associations/single_association.rb +28 -9
  14. data/lib/dynamoid/components.rb +2 -3
  15. data/lib/dynamoid/criteria/chain.rb +13 -9
  16. data/lib/dynamoid/criteria.rb +6 -7
  17. data/lib/dynamoid/dirty.rb +60 -63
  18. data/lib/dynamoid/document.rb +42 -12
  19. data/lib/dynamoid/errors.rb +2 -0
  20. data/lib/dynamoid/fields.rb +19 -37
  21. data/lib/dynamoid/finders.rb +9 -4
  22. data/lib/dynamoid/indexes.rb +45 -40
  23. data/lib/dynamoid/loadable.rb +6 -1
  24. data/lib/dynamoid/log/formatter.rb +19 -4
  25. data/lib/dynamoid/persistence/import.rb +4 -1
  26. data/lib/dynamoid/persistence/inc.rb +66 -0
  27. data/lib/dynamoid/persistence/save.rb +52 -5
  28. data/lib/dynamoid/persistence/update_fields.rb +1 -1
  29. data/lib/dynamoid/persistence/update_validations.rb +1 -1
  30. data/lib/dynamoid/persistence/upsert.rb +1 -1
  31. data/lib/dynamoid/persistence.rb +149 -61
  32. data/lib/dynamoid/undumping.rb +18 -0
  33. data/lib/dynamoid/validations.rb +6 -0
  34. data/lib/dynamoid/version.rb +1 -1
  35. data/lib/dynamoid.rb +1 -0
  36. metadata +30 -50
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative 'aws_sdk_v3/query'
4
4
  require_relative 'aws_sdk_v3/scan'
5
+ require_relative 'aws_sdk_v3/execute_statement'
5
6
  require_relative 'aws_sdk_v3/create_table'
6
7
  require_relative 'aws_sdk_v3/batch_get_item'
7
8
  require_relative 'aws_sdk_v3/item_updater'
@@ -163,30 +164,28 @@ module Dynamoid
163
164
  def batch_write_item(table_name, objects, options = {})
164
165
  items = objects.map { |o| sanitize_item(o) }
165
166
 
166
- begin
167
- while items.present?
168
- batch = items.shift(BATCH_WRITE_ITEM_REQUESTS_LIMIT)
169
- requests = batch.map { |item| { put_request: { item: item } } }
170
-
171
- response = client.batch_write_item(
172
- {
173
- request_items: {
174
- table_name => requests
175
- },
176
- return_consumed_capacity: 'TOTAL',
177
- return_item_collection_metrics: 'SIZE'
178
- }.merge!(options)
179
- )
167
+ while items.present?
168
+ batch = items.shift(BATCH_WRITE_ITEM_REQUESTS_LIMIT)
169
+ requests = batch.map { |item| { put_request: { item: item } } }
180
170
 
181
- yield(response.unprocessed_items.present?) if block_given?
171
+ response = client.batch_write_item(
172
+ {
173
+ request_items: {
174
+ table_name => requests
175
+ },
176
+ return_consumed_capacity: 'TOTAL',
177
+ return_item_collection_metrics: 'SIZE'
178
+ }.merge!(options)
179
+ )
182
180
 
183
- if response.unprocessed_items.present?
184
- items += response.unprocessed_items[table_name].map { |r| r.put_request.item }
185
- end
181
+ yield(response.unprocessed_items.present?) if block_given?
182
+
183
+ if response.unprocessed_items.present?
184
+ items += response.unprocessed_items[table_name].map { |r| r.put_request.item }
186
185
  end
187
- rescue Aws::DynamoDB::Errors::ConditionalCheckFailedException => e
188
- raise Dynamoid::Errors::ConditionalCheckFailedException, e
189
186
  end
187
+ rescue Aws::DynamoDB::Errors::ConditionalCheckFailedException => e
188
+ raise Dynamoid::Errors::ConditionalCheckFailedException, e
190
189
  end
191
190
 
192
191
  # Get many items at once from DynamoDB. More efficient than getting each item individually.
@@ -258,17 +257,15 @@ module Dynamoid
258
257
  end
259
258
  end
260
259
 
261
- begin
262
- requests.map do |request_items|
263
- client.batch_write_item(
264
- request_items: request_items,
265
- return_consumed_capacity: 'TOTAL',
266
- return_item_collection_metrics: 'SIZE'
267
- )
268
- end
269
- rescue Aws::DynamoDB::Errors::ConditionalCheckFailedException => e
270
- raise Dynamoid::Errors::ConditionalCheckFailedException, e
260
+ requests.each do |items|
261
+ client.batch_write_item(
262
+ request_items: items,
263
+ return_consumed_capacity: 'TOTAL',
264
+ return_item_collection_metrics: 'SIZE'
265
+ )
271
266
  end
267
+ rescue Aws::DynamoDB::Errors::ConditionalCheckFailedException => e
268
+ raise Dynamoid::Errors::ConditionalCheckFailedException, e
272
269
  end
273
270
 
274
271
  # Create a table on DynamoDB. This usually takes a long time to complete.
@@ -397,7 +394,7 @@ module Dynamoid
397
394
  item = client.get_item(table_name: table_name,
398
395
  key: key_stanza(table, key, range_key),
399
396
  consistent_read: consistent_read)[:item]
400
- item ? result_item_to_hash(item) : nil
397
+ item ? item_to_hash(item) : nil
401
398
  end
402
399
 
403
400
  # Edits an existing item's attributes, or adds a new item to the table if it does not already exist. You can put, delete, or add attribute values
@@ -416,20 +413,19 @@ module Dynamoid
416
413
  conditions = options.delete(:conditions)
417
414
  table = describe_table(table_name)
418
415
 
419
- yield(iu = ItemUpdater.new(table, key, range_key))
416
+ item_updater = ItemUpdater.new(table, key, range_key)
417
+ yield(item_updater)
420
418
 
421
419
  raise "non-empty options: #{options}" unless options.empty?
422
420
 
423
- begin
424
- result = client.update_item(table_name: table_name,
425
- key: key_stanza(table, key, range_key),
426
- attribute_updates: iu.to_h,
427
- expected: expected_stanza(conditions),
428
- return_values: 'ALL_NEW')
429
- result_item_to_hash(result[:attributes])
430
- rescue Aws::DynamoDB::Errors::ConditionalCheckFailedException => e
431
- raise Dynamoid::Errors::ConditionalCheckFailedException, e
432
- end
421
+ result = client.update_item(table_name: table_name,
422
+ key: key_stanza(table, key, range_key),
423
+ attribute_updates: item_updater.attribute_updates,
424
+ expected: expected_stanza(conditions),
425
+ return_values: 'ALL_NEW')
426
+ item_to_hash(result[:attributes])
427
+ rescue Aws::DynamoDB::Errors::ConditionalCheckFailedException => e
428
+ raise Dynamoid::Errors::ConditionalCheckFailedException, e
433
429
  end
434
430
 
435
431
  # List all tables on DynamoDB.
@@ -459,17 +455,15 @@ module Dynamoid
459
455
  options ||= {}
460
456
  item = sanitize_item(object)
461
457
 
462
- begin
463
- client.put_item(
464
- {
465
- table_name: table_name,
466
- item: item,
467
- expected: expected_stanza(options)
468
- }.merge!(options)
469
- )
470
- rescue Aws::DynamoDB::Errors::ConditionalCheckFailedException => e
471
- raise Dynamoid::Errors::ConditionalCheckFailedException, e
472
- end
458
+ client.put_item(
459
+ {
460
+ table_name: table_name,
461
+ item: item,
462
+ expected: expected_stanza(options)
463
+ }.merge!(options)
464
+ )
465
+ rescue Aws::DynamoDB::Errors::ConditionalCheckFailedException => e
466
+ raise Dynamoid::Errors::ConditionalCheckFailedException, e
473
467
  end
474
468
 
475
469
  # Query the DynamoDB table. This employs DynamoDB's indexes so is generally faster than scanning, but is
@@ -496,7 +490,7 @@ module Dynamoid
496
490
 
497
491
  Query.new(client, table, options).call.each do |page|
498
492
  yielder.yield(
499
- page.items.map { |row| result_item_to_hash(row) },
493
+ page.items.map { |item| item_to_hash(item) },
500
494
  last_evaluated_key: page.last_evaluated_key
501
495
  )
502
496
  end
@@ -529,7 +523,7 @@ module Dynamoid
529
523
 
530
524
  Scan.new(client, table, conditions, options).call.each do |page|
531
525
  yielder.yield(
532
- page.items.map { |row| result_item_to_hash(row) },
526
+ page.items.map { |item| item_to_hash(item) },
533
527
  last_evaluated_key: page.last_evaluated_key
534
528
  )
535
529
  end
@@ -567,6 +561,28 @@ module Dynamoid
567
561
  describe_table(table_name, true).item_count
568
562
  end
569
563
 
564
+ # Run PartiQL query.
565
+ #
566
+ # Dynamoid.adapter.execute("SELECT * FROM users WHERE id = ?", ["758"])
567
+ #
568
+ # @param [String] statement PartiQL statement
569
+ # @param [Array] parameters a list of bind parameters
570
+ # @param [Hash] options
571
+ # @option [Boolean] consistent_read
572
+ # @return [[] | Array[Hash] | Enumerator::Lazy[Hash]] items when used a SELECT statement and empty Array otherwise
573
+ #
574
+ def execute(statement, parameters = [], options = {})
575
+ items = ExecuteStatement.new(client, statement, parameters, options).call
576
+
577
+ if items.is_a?(Array)
578
+ items
579
+ else
580
+ items.lazy.flat_map { |array| array }
581
+ end
582
+ rescue Aws::DynamoDB::Errors::ConditionalCheckFailedException
583
+ []
584
+ end
585
+
570
586
  protected
571
587
 
572
588
  #
@@ -612,10 +628,8 @@ module Dynamoid
612
628
  #
613
629
  # Converts a hash returned by get_item, scan, etc. into a key-value hash
614
630
  #
615
- def result_item_to_hash(item)
616
- {}.tap do |r|
617
- item.each { |k, v| r[k.to_sym] = v }
618
- end
631
+ def item_to_hash(hash)
632
+ hash.symbolize_keys
619
633
  end
620
634
 
621
635
  def sanitize_item(attributes)
@@ -64,18 +64,37 @@ module Dynamoid
64
64
  target == other
65
65
  end
66
66
 
67
- # Delegate methods we don't find directly to the target.
68
- #
69
- # @private
70
- # @since 0.2.0
71
- def method_missing(method, *args)
72
- if target.respond_to?(method)
73
- target.send(method, *args)
74
- else
75
- super
67
+ if ::RUBY_VERSION < '2.7'
68
+ # Delegate methods we don't find directly to the target.
69
+ #
70
+ # @private
71
+ # @since 0.2.0
72
+ def method_missing(method, *args, &block)
73
+ if target.respond_to?(method)
74
+ target.send(method, *args, &block)
75
+ else
76
+ super
77
+ end
78
+ end
79
+ else
80
+ # Delegate methods we don't find directly to the target.
81
+ #
82
+ # @private
83
+ # @since 0.2.0
84
+ def method_missing(method, *args, **kwargs, &block)
85
+ if target.respond_to?(method)
86
+ target.send(method, *args, **kwargs, &block)
87
+ else
88
+ super
89
+ end
76
90
  end
77
91
  end
78
92
 
93
+ # @private
94
+ def respond_to_missing?(method_name, include_private = false)
95
+ target.respond_to?(method_name, include_private) || super
96
+ end
97
+
79
98
  # @private
80
99
  def nil?
81
100
  target.nil?
@@ -11,10 +11,9 @@ module Dynamoid
11
11
  extend ActiveModel::Translation
12
12
  extend ActiveModel::Callbacks
13
13
 
14
- define_model_callbacks :create, :save, :destroy, :initialize, :update
14
+ define_model_callbacks :create, :save, :destroy, :update
15
+ define_model_callbacks :initialize, :find, :touch, only: :after
15
16
 
16
- before_create :set_created_at
17
- before_save :set_updated_at
18
17
  before_save :set_expires_field
19
18
  after_initialize :set_inheritance_field
20
19
  end
@@ -187,10 +187,10 @@ module Dynamoid
187
187
  def first(*args)
188
188
  n = args.first || 1
189
189
 
190
- return 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
- 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.
@@ -486,15 +486,17 @@ module Dynamoid
486
486
  # @return [Array]
487
487
  def pluck(*args)
488
488
  fields = args.map(&:to_sym)
489
- @project = fields
489
+
490
+ scope = dup
491
+ scope.project(*fields)
490
492
 
491
493
  if fields.many?
492
- items.map do |item|
494
+ scope.items.map do |item|
493
495
  fields.map { |key| Undumping.undump_field(item[key], source.attributes[key]) }
494
496
  end.to_a
495
497
  else
496
498
  key = fields.first
497
- items.map { |item| Undumping.undump_field(item[key], source.attributes[key]) }.to_a
499
+ scope.items.map { |item| Undumping.undump_field(item[key], source.attributes[key]) }.to_a
498
500
  end
499
501
  end
500
502
 
@@ -513,6 +515,7 @@ module Dynamoid
513
515
  def items
514
516
  raw_pages.lazy.flat_map { |items, _| items }
515
517
  end
518
+ protected :items
516
519
 
517
520
  # Arrays of records, sized based on the actual pages produced by DynamoDB
518
521
  #
@@ -522,6 +525,7 @@ module Dynamoid
522
525
  def pages
523
526
  raw_pages.lazy.map do |items, options|
524
527
  models = items.map { |i| source.from_database(i) }
528
+ models.each { |m| m.run_callbacks :find }
525
529
  [models, options]
526
530
  end.each
527
531
  end
@@ -568,7 +572,7 @@ module Dynamoid
568
572
 
569
573
  def issue_scan_warning
570
574
  Dynamoid.logger.warn 'Queries without an index are forced to use scan and are generally much slower than indexed queries!'
571
- Dynamoid.logger.warn "You can index this query by adding index declaration to #{source.to_s.downcase}.rb:"
575
+ Dynamoid.logger.warn "You can index this query by adding index declaration to #{source.to_s.underscore}.rb:"
572
576
  Dynamoid.logger.warn "* global_secondary_index hash_key: 'some-name', range_key: 'some-another-name'"
573
577
  Dynamoid.logger.warn "* local_secondary_index range_key: 'some-name'"
574
578
  Dynamoid.logger.warn "Not indexed attributes: #{query.keys.sort.collect { |name| ":#{name}" }.join(', ')}"
@@ -784,9 +788,9 @@ module Dynamoid
784
788
  condition = {}
785
789
  type = @source.inheritance_field
786
790
 
787
- if @source.attributes.key?(type)
788
- class_names = @source.deep_subclasses.map(&:name) << @source.name
789
- 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
790
794
  end
791
795
 
792
796
  condition
@@ -9,12 +9,15 @@ module Dynamoid
9
9
 
10
10
  # @private
11
11
  module ClassMethods
12
- %i[where consistent all first last delete_all destroy_all each record_limit scan_limit batch start scan_index_forward find_by_pages project pluck].each do |meth|
12
+ %i[
13
+ where consistent all first last delete_all destroy_all each record_limit
14
+ scan_limit batch start scan_index_forward find_by_pages project pluck
15
+ ].each do |name|
13
16
  # Return a criteria chain in response to a method that will begin or end a chain. For more information,
14
17
  # see Dynamoid::Criteria::Chain.
15
18
  #
16
19
  # @since 0.2.0
17
- define_method(meth) do |*args, &blk|
20
+ define_method(name) do |*args, &blk|
18
21
  # Don't use keywork arguments delegating (with **kw). It works in
19
22
  # different way in different Ruby versions: <= 2.6, 2.7, 3.0 and in some
20
23
  # future 3.x versions. Providing that there are no downstream methods
@@ -23,11 +26,7 @@ module Dynamoid
23
26
  # https://eregon.me/blog/2019/11/10/the-delegation-challenge-of-ruby27.html
24
27
 
25
28
  chain = Dynamoid::Criteria::Chain.new(self)
26
- if args
27
- chain.send(meth, *args, &blk)
28
- else
29
- chain.send(meth, &blk)
30
- end
29
+ chain.send(name, *args, &blk)
31
30
  end
32
31
  end
33
32
  end
@@ -25,32 +25,27 @@ module Dynamoid
25
25
  # @private
26
26
  module ClassMethods
27
27
  def update_fields(*)
28
- if model = super
29
- model.send(:clear_changes_information)
28
+ super.tap do |model|
29
+ model.clear_changes_information if model
30
30
  end
31
- model
32
31
  end
33
32
 
34
33
  def upsert(*)
35
- if model = super
36
- model.send(:clear_changes_information)
34
+ super.tap do |model|
35
+ model.clear_changes_information if model
37
36
  end
38
- model
39
37
  end
40
38
 
41
39
  def from_database(*)
42
- super.tap do |m|
43
- m.send(:clear_changes_information)
44
- end
40
+ super.tap(&:clear_changes_information)
45
41
  end
46
42
  end
47
43
 
48
44
  # @private
49
45
  def save(*)
50
- if status = super
51
- changes_applied
46
+ super.tap do |status|
47
+ changes_applied if status
52
48
  end
53
- status
54
49
  end
55
50
 
56
51
  # @private
@@ -113,7 +108,7 @@ module Dynamoid
113
108
  #
114
109
  # @return [ActiveSupport::HashWithIndifferentAccess]
115
110
  def changes
116
- ActiveSupport::HashWithIndifferentAccess[changed.map { |attr| [attr, attribute_change(attr)] }]
111
+ ActiveSupport::HashWithIndifferentAccess[changed.map { |name| [name, attribute_change(name)] }]
117
112
  end
118
113
 
119
114
  # Returns a hash of attributes that were changed before the model was saved.
@@ -140,6 +135,25 @@ module Dynamoid
140
135
  @changed_attributes ||= ActiveSupport::HashWithIndifferentAccess.new
141
136
  end
142
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
+
143
157
  # Handle <tt>*_changed?</tt> for +method_missing+.
144
158
  #
145
159
  # person.attribute_changed?(:name) # => true
@@ -148,14 +162,14 @@ module Dynamoid
148
162
  # person.attribute_changed?(:name, from: 'Alice', to: 'Bod')
149
163
  #
150
164
  # @private
151
- # @param attr [Symbol] attribute name
165
+ # @param name [Symbol] attribute name
152
166
  # @param options [Hash] conditions on +from+ and +to+ value (optional)
153
167
  # @option options [Symbol] :from previous attribute value
154
168
  # @option options [Symbol] :to current attribute value
155
- def attribute_changed?(attr, options = {})
156
- result = changes_include?(attr)
157
- result &&= options[:to] == __send__(attr) if options.key?(:to)
158
- 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)
159
173
  result
160
174
  end
161
175
 
@@ -166,16 +180,16 @@ module Dynamoid
166
180
  # person.attribute_was(:name) # => "Alice"
167
181
  #
168
182
  # @private
169
- # @param attr [Symbol] attribute name
170
- def attribute_was(attr)
171
- 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)
172
186
  end
173
187
 
174
188
  # Restore all previous data of the provided attributes.
175
189
  #
176
190
  # @param attributes [Array[Symbol]] a list of attribute names
177
- def restore_attributes(attributes = changed)
178
- attributes.each { |attr| restore_attribute! attr }
191
+ def restore_attributes(names = changed)
192
+ names.each { |name| restore_attribute! name }
179
193
  end
180
194
 
181
195
  # Handles <tt>*_previously_changed?</tt> for +method_missing+.
@@ -186,10 +200,10 @@ module Dynamoid
186
200
  # person.attribute_changed?(:name) # => true
187
201
  #
188
202
  # @private
189
- # @param attr [Symbol] attribute name
203
+ # @param name [Symbol] attribute name
190
204
  # @return [true|false]
191
- def attribute_previously_changed?(attr)
192
- previous_changes_include?(attr)
205
+ def attribute_previously_changed?(name)
206
+ previous_changes_include?(name)
193
207
  end
194
208
 
195
209
  # Handles <tt>*_previous_change</tt> for +method_missing+.
@@ -200,61 +214,49 @@ module Dynamoid
200
214
  # person.attribute_previously_changed(:name) # => ["Alice", "Bob"]
201
215
  #
202
216
  # @private
203
- # @param attr [Symbol]
217
+ # @param name [Symbol]
204
218
  # @return [Array]
205
- def attribute_previous_change(attr)
206
- previous_changes[attr] if attribute_previously_changed?(attr)
219
+ def attribute_previous_change(name)
220
+ previous_changes[name] if attribute_previously_changed?(name)
207
221
  end
208
222
 
209
223
  private
210
224
 
211
- def changes_include?(attr_name)
212
- attributes_changed_by_setter.include?(attr_name)
225
+ def changes_include?(name)
226
+ attributes_changed_by_setter.include?(name)
213
227
  end
214
228
  alias attribute_changed_by_setter? changes_include?
215
229
 
216
- # Removes current changes and makes them accessible through +previous_changes+.
217
- def changes_applied # :doc:
218
- @previously_changed = changes
219
- @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
220
- end
221
-
222
- # Clear all dirty data: current changes and previous changes.
223
- def clear_changes_information # :doc:
224
- @previously_changed = ActiveSupport::HashWithIndifferentAccess.new
225
- @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
226
- end
227
-
228
230
  # Handle <tt>*_change</tt> for +method_missing+.
229
- def attribute_change(attr)
230
- [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)
231
233
  end
232
234
 
233
235
  # Handle <tt>*_will_change!</tt> for +method_missing+.
234
- def attribute_will_change!(attr)
235
- return if attribute_changed?(attr)
236
+ def attribute_will_change!(name)
237
+ return if attribute_changed?(name)
236
238
 
237
239
  begin
238
- value = __send__(attr)
240
+ value = read_attribute(name)
239
241
  value = value.duplicable? ? value.clone : value
240
242
  rescue TypeError, NoMethodError
241
243
  end
242
244
 
243
- set_attribute_was(attr, value)
245
+ set_attribute_was(name, value)
244
246
  end
245
247
 
246
248
  # Handle <tt>restore_*!</tt> for +method_missing+.
247
- def restore_attribute!(attr)
248
- if attribute_changed?(attr)
249
- __send__("#{attr}=", changed_attributes[attr])
250
- 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])
251
253
  end
252
254
  end
253
255
 
254
- # Returns +true+ if attr_name were changed before the model was saved,
256
+ # Returns +true+ if name were changed before the model was saved,
255
257
  # +false+ otherwise.
256
- def previous_changes_include?(attr_name)
257
- previous_changes.include?(attr_name)
258
+ def previous_changes_include?(name)
259
+ previous_changes.include?(name)
258
260
  end
259
261
 
260
262
  # This is necessary because `changed_attributes` might be overridden in
@@ -262,13 +264,8 @@ module Dynamoid
262
264
  alias attributes_changed_by_setter changed_attributes
263
265
 
264
266
  # Force an attribute to have a particular "before" value
265
- def set_attribute_was(attr, old_value)
266
- attributes_changed_by_setter[attr] = old_value
267
- end
268
-
269
- # Remove changes information for the provided attributes.
270
- def clear_attribute_changes(attributes)
271
- attributes_changed_by_setter.except!(*attributes)
267
+ def set_attribute_was(name, old_value)
268
+ attributes_changed_by_setter[name] = old_value
272
269
  end
273
270
  end
274
271
  end