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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +246 -244
- data/LICENSE.txt +2 -2
- data/README.md +134 -55
- data/SECURITY.md +17 -0
- data/dynamoid.gemspec +66 -0
- data/lib/dynamoid/adapter.rb +7 -9
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/batch_get_item.rb +2 -2
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/execute_statement.rb +62 -0
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/item_updater.rb +29 -15
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3/middleware/limit.rb +3 -0
- data/lib/dynamoid/adapter_plugin/aws_sdk_v3.rb +73 -59
- data/lib/dynamoid/associations/single_association.rb +28 -9
- data/lib/dynamoid/components.rb +2 -3
- data/lib/dynamoid/criteria/chain.rb +13 -9
- data/lib/dynamoid/criteria.rb +6 -7
- data/lib/dynamoid/dirty.rb +60 -63
- data/lib/dynamoid/document.rb +42 -12
- data/lib/dynamoid/errors.rb +2 -0
- data/lib/dynamoid/fields.rb +19 -37
- data/lib/dynamoid/finders.rb +9 -4
- data/lib/dynamoid/indexes.rb +45 -40
- data/lib/dynamoid/loadable.rb +6 -1
- data/lib/dynamoid/log/formatter.rb +19 -4
- data/lib/dynamoid/persistence/import.rb +4 -1
- data/lib/dynamoid/persistence/inc.rb +66 -0
- data/lib/dynamoid/persistence/save.rb +52 -5
- data/lib/dynamoid/persistence/update_fields.rb +1 -1
- data/lib/dynamoid/persistence/update_validations.rb +1 -1
- data/lib/dynamoid/persistence/upsert.rb +1 -1
- data/lib/dynamoid/persistence.rb +149 -61
- data/lib/dynamoid/undumping.rb +18 -0
- data/lib/dynamoid/validations.rb +6 -0
- data/lib/dynamoid/version.rb +1 -1
- data/lib/dynamoid.rb +1 -0
- 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
|
-
|
167
|
-
|
168
|
-
|
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
|
-
|
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
|
-
|
184
|
-
|
185
|
-
|
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
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
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 ?
|
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
|
-
|
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
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
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
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
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 { |
|
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 { |
|
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
|
616
|
-
|
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
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
target.
|
74
|
-
|
75
|
-
|
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?
|
data/lib/dynamoid/components.rb
CHANGED
@@ -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, :
|
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
|
-
|
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.
|
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
|
-
|
789
|
-
condition[:"#{type}.in"] =
|
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
|
data/lib/dynamoid/criteria.rb
CHANGED
@@ -9,12 +9,15 @@ module Dynamoid
|
|
9
9
|
|
10
10
|
# @private
|
11
11
|
module ClassMethods
|
12
|
-
%i[
|
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(
|
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
|
-
|
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
|
data/lib/dynamoid/dirty.rb
CHANGED
@@ -25,32 +25,27 @@ module Dynamoid
|
|
25
25
|
# @private
|
26
26
|
module ClassMethods
|
27
27
|
def update_fields(*)
|
28
|
-
|
29
|
-
model.
|
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
|
-
|
36
|
-
model.
|
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
|
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
|
-
|
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 { |
|
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
|
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?(
|
156
|
-
result = changes_include?(
|
157
|
-
result &&= options[:to] ==
|
158
|
-
result &&= options[:from] == changed_attributes[
|
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
|
170
|
-
def attribute_was(
|
171
|
-
attribute_changed?(
|
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(
|
178
|
-
|
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
|
203
|
+
# @param name [Symbol] attribute name
|
190
204
|
# @return [true|false]
|
191
|
-
def attribute_previously_changed?(
|
192
|
-
previous_changes_include?(
|
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
|
217
|
+
# @param name [Symbol]
|
204
218
|
# @return [Array]
|
205
|
-
def attribute_previous_change(
|
206
|
-
previous_changes[
|
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?(
|
212
|
-
attributes_changed_by_setter.include?(
|
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(
|
230
|
-
[changed_attributes[
|
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!(
|
235
|
-
return if attribute_changed?(
|
236
|
+
def attribute_will_change!(name)
|
237
|
+
return if attribute_changed?(name)
|
236
238
|
|
237
239
|
begin
|
238
|
-
value =
|
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(
|
245
|
+
set_attribute_was(name, value)
|
244
246
|
end
|
245
247
|
|
246
248
|
# Handle <tt>restore_*!</tt> for +method_missing+.
|
247
|
-
def restore_attribute!(
|
248
|
-
if attribute_changed?(
|
249
|
-
|
250
|
-
clear_attribute_changes([
|
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
|
256
|
+
# Returns +true+ if name were changed before the model was saved,
|
255
257
|
# +false+ otherwise.
|
256
|
-
def previous_changes_include?(
|
257
|
-
previous_changes.include?(
|
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(
|
266
|
-
attributes_changed_by_setter[
|
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
|