dynamoid 3.7.1 → 3.9.0

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