blob: 0bcfa5f00dba6e0990c7dbf66c426a4eae9f7dbf (
plain) (
blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
|
require 'active_support/core_ext/module/attribute_accessors'
require 'active_record/attribute_mutation_tracker'
module ActiveRecord
module AttributeMethods
module Dirty # :nodoc:
extend ActiveSupport::Concern
include ActiveModel::Dirty
included do
if self < ::ActiveRecord::Timestamp
raise "You cannot include Dirty after Timestamp"
end
class_attribute :partial_writes, instance_writer: false
self.partial_writes = true
end
# Attempts to +save+ the record and clears changed attributes if successful.
def save(*)
if status = super
changes_applied
end
status
end
# Attempts to <tt>save!</tt> the record and clears changed attributes if successful.
def save!(*)
super.tap do
changes_applied
end
end
# <tt>reload</tt> the record and clears changed attributes.
def reload(*)
super.tap do
@mutation_tracker = nil
@previous_mutation_tracker = nil
@changed_attributes = HashWithIndifferentAccess.new
end
end
def initialize_dup(other) # :nodoc:
super
@attributes = self.class._default_attributes.map do |attr|
attr.with_value_from_user(@attributes.fetch_value(attr.name))
end
@mutation_tracker = nil
end
def changes_applied
@previous_mutation_tracker = mutation_tracker
@changed_attributes = HashWithIndifferentAccess.new
store_original_attributes
end
def clear_changes_information
@previous_mutation_tracker = nil
@changed_attributes = HashWithIndifferentAccess.new
store_original_attributes
end
def raw_write_attribute(attr_name, *)
result = super
clear_attribute_change(attr_name)
result
end
def clear_attribute_changes(attr_names)
super
attr_names.each do |attr_name|
clear_attribute_change(attr_name)
end
end
def changed_attributes
# This should only be set by methods which will call changed_attributes
# multiple times when it is known that the computed value cannot change.
if defined?(@cached_changed_attributes)
@cached_changed_attributes
else
super.reverse_merge(mutation_tracker.changed_values).freeze
end
end
def changes
cache_changed_attributes do
super
end
end
def previous_changes
previous_mutation_tracker.changes
end
def attribute_changed_in_place?(attr_name)
mutation_tracker.changed_in_place?(attr_name)
end
private
def mutation_tracker
unless defined?(@mutation_tracker)
@mutation_tracker = nil
end
@mutation_tracker ||= AttributeMutationTracker.new(@attributes)
end
def changes_include?(attr_name)
super || mutation_tracker.changed?(attr_name)
end
def clear_attribute_change(attr_name)
mutation_tracker.forget_change(attr_name)
end
def _update_record(*)
partial_writes? ? super(keys_for_partial_write) : super
end
def _create_record(*)
partial_writes? ? super(keys_for_partial_write) : super
end
def keys_for_partial_write
changed & self.class.column_names
end
def store_original_attributes
@attributes = @attributes.map(&:forgetting_assignment)
@mutation_tracker = nil
end
def previous_mutation_tracker
@previous_mutation_tracker ||= NullMutationTracker.instance
end
def cache_changed_attributes
@cached_changed_attributes = changed_attributes
yield
ensure
clear_changed_attributes_cache
end
def clear_changed_attributes_cache
remove_instance_variable(:@cached_changed_attributes) if defined?(@cached_changed_attributes)
end
end
end
end
|