duck_record 0.0.9.1 → 0.0.10
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/Rakefile +13 -13
- data/lib/duck_record/associations/association.rb +15 -15
- data/lib/duck_record/associations/collection_association.rb +8 -8
- data/lib/duck_record/associations/collection_proxy.rb +1 -1
- data/lib/duck_record/associations/has_many_association.rb +0 -1
- data/lib/duck_record/associations/singular_association.rb +6 -6
- data/lib/duck_record/attribute/user_provided_default.rb +2 -2
- data/lib/duck_record/attribute.rb +70 -70
- data/lib/duck_record/attribute_assignment.rb +74 -74
- data/lib/duck_record/attribute_methods/before_type_cast.rb +9 -9
- data/lib/duck_record/attribute_methods/dirty.rb +29 -29
- data/lib/duck_record/attribute_methods/read.rb +27 -27
- data/lib/duck_record/attribute_methods/write.rb +20 -20
- data/lib/duck_record/attribute_methods.rb +8 -8
- data/lib/duck_record/attribute_mutation_tracker.rb +4 -4
- data/lib/duck_record/attribute_set/yaml_encoder.rb +5 -5
- data/lib/duck_record/attribute_set.rb +5 -5
- data/lib/duck_record/attributes.rb +14 -14
- data/lib/duck_record/base.rb +18 -18
- data/lib/duck_record/callbacks.rb +1 -1
- data/lib/duck_record/core.rb +35 -35
- data/lib/duck_record/inheritance.rb +25 -25
- data/lib/duck_record/model_schema.rb +10 -11
- data/lib/duck_record/nested_attributes.rb +149 -149
- data/lib/duck_record/reflection.rb +14 -14
- data/lib/duck_record/type/array.rb +6 -6
- data/lib/duck_record/type/registry.rb +21 -21
- data/lib/duck_record/type/serialized.rb +8 -8
- data/lib/duck_record/type/time.rb +0 -1
- data/lib/duck_record/type/unsigned_integer.rb +6 -6
- data/lib/duck_record/type.rb +16 -14
- data/lib/duck_record/validations/uniqueness_on_real_record.rb +45 -45
- data/lib/duck_record/validations.rb +6 -6
- data/lib/duck_record/version.rb +1 -1
- data/lib/duck_record.rb +7 -7
- metadata +7 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b939fb448db977122db882494b644b720cbc693a
|
4
|
+
data.tar.gz: c8a2ffdb2f4571f75fc1935f6719fa285faf48f1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5512c1065105dfcb7c0d79e320e1daf27d6bf7d9cecdadcf080cdb9b2150a9bfe7cb5b2ad0b45ab5912c3189af831b005a8544d7f3acc5ecb349c8ce12a44456
|
7
|
+
data.tar.gz: ef2cc020e9e153e00d5ef5e3c38dea6bba9d6a6446f10173a177a226b24f0b9198d10c194a0dc9dac4aedd3c770cc55e4c522e1ef89efa3609d9282b34fbd3b2
|
data/Rakefile
CHANGED
@@ -1,27 +1,27 @@
|
|
1
1
|
begin
|
2
|
-
require
|
2
|
+
require "bundler/setup"
|
3
3
|
rescue LoadError
|
4
|
-
puts
|
4
|
+
puts "You must `gem install bundler` and `bundle install` to run rake tasks"
|
5
5
|
end
|
6
6
|
|
7
|
-
require
|
7
|
+
require "rdoc/task"
|
8
8
|
|
9
9
|
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
-
rdoc.rdoc_dir =
|
11
|
-
rdoc.title =
|
12
|
-
rdoc.options <<
|
13
|
-
rdoc.rdoc_files.include(
|
14
|
-
rdoc.rdoc_files.include(
|
10
|
+
rdoc.rdoc_dir = "rdoc"
|
11
|
+
rdoc.title = "DuckRecord"
|
12
|
+
rdoc.options << "--line-numbers"
|
13
|
+
rdoc.rdoc_files.include("README.md")
|
14
|
+
rdoc.rdoc_files.include("lib/**/*.rb")
|
15
15
|
end
|
16
16
|
|
17
|
-
require
|
17
|
+
require "bundler/gem_tasks"
|
18
18
|
|
19
|
-
require
|
19
|
+
require "rake/testtask"
|
20
20
|
|
21
21
|
Rake::TestTask.new(:test) do |t|
|
22
|
-
t.libs <<
|
23
|
-
t.libs <<
|
24
|
-
t.pattern =
|
22
|
+
t.libs << "lib"
|
23
|
+
t.libs << "test"
|
24
|
+
t.pattern = "test/**/*_test.rb"
|
25
25
|
t.verbose = false
|
26
26
|
end
|
27
27
|
|
@@ -68,25 +68,25 @@ module DuckRecord
|
|
68
68
|
|
69
69
|
private
|
70
70
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
71
|
+
# Raises ActiveRecord::AssociationTypeMismatch unless +record+ is of
|
72
|
+
# the kind of the class of the associated objects. Meant to be used as
|
73
|
+
# a sanity check when you are about to assign an associated record.
|
74
|
+
def raise_on_type_mismatch!(record)
|
75
|
+
unless record.is_a?(reflection.klass)
|
76
|
+
fresh_class = reflection.class_name.safe_constantize
|
77
|
+
unless fresh_class && record.is_a?(fresh_class)
|
78
|
+
message = "#{reflection.class_name}(##{reflection.klass.object_id}) expected, "\
|
79
|
+
"got #{record.inspect} which is an instance of #{record.class}(##{record.class.object_id})"
|
80
|
+
raise ActiveRecord::AssociationTypeMismatch, message
|
81
|
+
end
|
81
82
|
end
|
82
83
|
end
|
83
|
-
end
|
84
84
|
|
85
|
-
|
86
|
-
|
87
|
-
|
85
|
+
def build_record(attributes)
|
86
|
+
reflection.build_association(attributes) do |record|
|
87
|
+
initialize_attributes(record, attributes)
|
88
|
+
end
|
88
89
|
end
|
89
|
-
end
|
90
90
|
end
|
91
91
|
end
|
92
92
|
end
|
@@ -172,16 +172,16 @@ module DuckRecord
|
|
172
172
|
|
173
173
|
private
|
174
174
|
|
175
|
-
|
176
|
-
|
177
|
-
|
175
|
+
def callback(method, record)
|
176
|
+
callbacks_for(method).each do |callback|
|
177
|
+
callback.call(method, owner, record)
|
178
|
+
end
|
178
179
|
end
|
179
|
-
end
|
180
180
|
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
181
|
+
def callbacks_for(callback_name)
|
182
|
+
full_callback_name = "#{callback_name}_for_#{reflection.name}"
|
183
|
+
owner.class.send(full_callback_name)
|
184
|
+
end
|
185
185
|
end
|
186
186
|
end
|
187
187
|
end
|
@@ -27,13 +27,13 @@ module DuckRecord
|
|
27
27
|
|
28
28
|
private
|
29
29
|
|
30
|
-
|
31
|
-
|
32
|
-
|
30
|
+
def replace(_record)
|
31
|
+
raise NotImplementedError, "Subclasses must implement a replace(record) method"
|
32
|
+
end
|
33
33
|
|
34
|
-
|
35
|
-
|
36
|
-
|
34
|
+
def set_new_record(record)
|
35
|
+
replace(record)
|
36
|
+
end
|
37
37
|
end
|
38
38
|
end
|
39
39
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "duck_record/attribute"
|
2
2
|
|
3
3
|
module DuckRecord
|
4
4
|
class Attribute # :nodoc:
|
@@ -24,7 +24,7 @@ module DuckRecord
|
|
24
24
|
# Workaround for Ruby 2.2 "private attribute?" warning.
|
25
25
|
protected
|
26
26
|
|
27
|
-
|
27
|
+
attr_reader :user_provided_value
|
28
28
|
end
|
29
29
|
end
|
30
30
|
end
|
@@ -110,104 +110,104 @@ module DuckRecord
|
|
110
110
|
|
111
111
|
protected
|
112
112
|
|
113
|
-
|
114
|
-
|
113
|
+
attr_reader :original_attribute
|
114
|
+
alias_method :assigned?, :original_attribute
|
115
115
|
|
116
|
-
|
117
|
-
|
118
|
-
|
116
|
+
def initialize_dup(other)
|
117
|
+
if defined?(@value) && @value.duplicable?
|
118
|
+
@value = @value.dup
|
119
|
+
end
|
119
120
|
end
|
120
|
-
end
|
121
121
|
|
122
|
-
|
123
|
-
|
124
|
-
end
|
125
|
-
|
126
|
-
def original_value_for_database
|
127
|
-
if assigned?
|
128
|
-
original_attribute.original_value_for_database
|
129
|
-
else
|
130
|
-
_original_value_for_database
|
122
|
+
def changed_from_assignment?
|
123
|
+
assigned? && type.changed?(original_value, value, value_before_type_cast)
|
131
124
|
end
|
132
|
-
end
|
133
125
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
type.deserialize(value)
|
126
|
+
def original_value_for_database
|
127
|
+
if assigned?
|
128
|
+
original_attribute.original_value_for_database
|
129
|
+
else
|
130
|
+
_original_value_for_database
|
131
|
+
end
|
141
132
|
end
|
142
133
|
|
143
134
|
def _original_value_for_database
|
144
|
-
|
135
|
+
type.serialize(original_value)
|
145
136
|
end
|
146
|
-
end
|
147
137
|
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
138
|
+
class FromDatabase < Attribute # :nodoc:
|
139
|
+
def type_cast(value)
|
140
|
+
type.deserialize(value)
|
141
|
+
end
|
152
142
|
|
153
|
-
|
154
|
-
|
143
|
+
def _original_value_for_database
|
144
|
+
value_before_type_cast
|
145
|
+
end
|
155
146
|
end
|
156
|
-
end
|
157
147
|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
148
|
+
class FromUser < Attribute # :nodoc:
|
149
|
+
def type_cast(value)
|
150
|
+
type.cast(value)
|
151
|
+
end
|
162
152
|
|
163
|
-
|
164
|
-
|
153
|
+
def came_from_user?
|
154
|
+
true
|
155
|
+
end
|
165
156
|
end
|
166
|
-
end
|
167
157
|
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
158
|
+
class WithCastValue < Attribute # :nodoc:
|
159
|
+
def type_cast(value)
|
160
|
+
value
|
161
|
+
end
|
172
162
|
|
173
|
-
|
174
|
-
|
163
|
+
def changed_in_place?
|
164
|
+
false
|
165
|
+
end
|
175
166
|
end
|
176
167
|
|
177
|
-
|
178
|
-
|
179
|
-
|
168
|
+
class Null < Attribute # :nodoc:
|
169
|
+
def initialize(name)
|
170
|
+
super(name, nil, Type::Value.new)
|
171
|
+
end
|
180
172
|
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
alias_method :with_value_from_user, :with_value_from_database
|
185
|
-
end
|
173
|
+
def type_cast(*)
|
174
|
+
nil
|
175
|
+
end
|
186
176
|
|
187
|
-
|
188
|
-
|
177
|
+
def with_type(type)
|
178
|
+
self.class.with_cast_value(name, nil, type)
|
179
|
+
end
|
189
180
|
|
190
|
-
|
191
|
-
|
181
|
+
def with_value_from_database(value)
|
182
|
+
raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{name}`"
|
183
|
+
end
|
184
|
+
alias_method :with_value_from_user, :with_value_from_database
|
192
185
|
end
|
193
186
|
|
194
|
-
|
195
|
-
|
196
|
-
|
187
|
+
class Uninitialized < Attribute # :nodoc:
|
188
|
+
UNINITIALIZED_ORIGINAL_VALUE = Object.new
|
189
|
+
|
190
|
+
def initialize(name, type)
|
191
|
+
super(name, nil, type)
|
197
192
|
end
|
198
|
-
end
|
199
193
|
|
200
|
-
|
201
|
-
|
202
|
-
|
194
|
+
def value
|
195
|
+
if block_given?
|
196
|
+
yield name
|
197
|
+
end
|
198
|
+
end
|
203
199
|
|
204
|
-
|
205
|
-
|
200
|
+
def original_value
|
201
|
+
UNINITIALIZED_ORIGINAL_VALUE
|
202
|
+
end
|
206
203
|
|
207
|
-
|
208
|
-
|
204
|
+
def value_for_database
|
205
|
+
end
|
206
|
+
|
207
|
+
def initialized?
|
208
|
+
false
|
209
|
+
end
|
209
210
|
end
|
210
|
-
|
211
|
-
private_constant :FromDatabase, :FromUser, :Null, :Uninitialized, :WithCastValue
|
211
|
+
private_constant :FromDatabase, :FromUser, :Null, :Uninitialized, :WithCastValue
|
212
212
|
end
|
213
213
|
end
|
@@ -1,5 +1,5 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require "active_support/core_ext/hash/keys"
|
2
|
+
require "active_model/forbidden_attributes_protection"
|
3
3
|
|
4
4
|
module DuckRecord
|
5
5
|
module AttributeAssignment
|
@@ -23,97 +23,97 @@ module DuckRecord
|
|
23
23
|
|
24
24
|
private
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
|
26
|
+
def _assign_attributes(attributes, force_write_readonly: false)
|
27
|
+
multi_parameter_attributes = {}
|
28
|
+
nested_parameter_attributes = {}
|
29
29
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
30
|
+
attributes.each do |k, v|
|
31
|
+
if k.include?("(")
|
32
|
+
multi_parameter_attributes[k] = attributes.delete(k)
|
33
|
+
elsif v.is_a?(Hash)
|
34
|
+
nested_parameter_attributes[k] = attributes.delete(k)
|
35
|
+
end
|
35
36
|
end
|
36
|
-
end
|
37
37
|
|
38
|
-
|
39
|
-
|
40
|
-
|
38
|
+
attributes.each do |k, v|
|
39
|
+
_assign_attribute(k, v, force_write_readonly: force_write_readonly)
|
40
|
+
end
|
41
41
|
|
42
|
-
|
43
|
-
|
44
|
-
|
42
|
+
unless nested_parameter_attributes.empty?
|
43
|
+
assign_nested_parameter_attributes(nested_parameter_attributes, force_write_readonly: force_write_readonly)
|
44
|
+
end
|
45
45
|
|
46
|
-
|
47
|
-
|
46
|
+
unless multi_parameter_attributes.empty?
|
47
|
+
assign_multiparameter_attributes(multi_parameter_attributes, force_write_readonly: force_write_readonly)
|
48
|
+
end
|
48
49
|
end
|
49
|
-
end
|
50
50
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
51
|
+
# Assign any deferred nested attributes after the base attributes have been set.
|
52
|
+
def assign_nested_parameter_attributes(pairs, force_write_readonly: false)
|
53
|
+
pairs.each { |k, v| _assign_attribute(k, v, force_write_readonly: force_write_readonly) }
|
54
|
+
end
|
55
55
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
56
|
+
# Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done
|
57
|
+
# by calling new on the column type or aggregation type (through composed_of) object with these parameters.
|
58
|
+
# So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
|
59
|
+
# written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
|
60
|
+
# parentheses to have the parameters typecasted before they're used in the constructor. Use i for Integer and
|
61
|
+
# f for Float. If all the values for a given attribute are empty, the attribute will be set to +nil+.
|
62
|
+
def assign_multiparameter_attributes(pairs, force_write_readonly: false)
|
63
|
+
execute_callstack_for_multiparameter_attributes(
|
64
|
+
extract_callstack_for_multiparameter_attributes(pairs),
|
65
|
+
force_write_readonly: force_write_readonly
|
66
|
+
)
|
67
|
+
end
|
68
68
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
69
|
+
def execute_callstack_for_multiparameter_attributes(callstack, force_write_readonly: false)
|
70
|
+
errors = []
|
71
|
+
callstack.each do |name, values_with_empty_parameters|
|
72
|
+
begin
|
73
|
+
if values_with_empty_parameters.each_value.all?(&:nil?)
|
74
|
+
values = nil
|
75
|
+
else
|
76
|
+
values = values_with_empty_parameters
|
77
|
+
end
|
78
|
+
send("#{name}=", values, force_write_readonly: force_write_readonly)
|
79
|
+
rescue => ex
|
80
|
+
errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name} (#{ex.message})", ex, name)
|
77
81
|
end
|
78
|
-
|
79
|
-
|
80
|
-
|
82
|
+
end
|
83
|
+
unless errors.empty?
|
84
|
+
error_descriptions = errors.map(&:message).join(",")
|
85
|
+
raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes [#{error_descriptions}]"
|
81
86
|
end
|
82
87
|
end
|
83
|
-
unless errors.empty?
|
84
|
-
error_descriptions = errors.map(&:message).join(',')
|
85
|
-
raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes [#{error_descriptions}]"
|
86
|
-
end
|
87
|
-
end
|
88
88
|
|
89
|
-
|
90
|
-
|
89
|
+
def extract_callstack_for_multiparameter_attributes(pairs)
|
90
|
+
attributes = {}
|
91
91
|
|
92
|
-
|
93
|
-
|
94
|
-
|
92
|
+
pairs.each do |(multiparameter_name, value)|
|
93
|
+
attribute_name = multiparameter_name.split("(").first
|
94
|
+
attributes[attribute_name] ||= {}
|
95
95
|
|
96
|
-
|
97
|
-
|
98
|
-
|
96
|
+
parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value)
|
97
|
+
attributes[attribute_name][find_parameter_position(multiparameter_name)] ||= parameter_value
|
98
|
+
end
|
99
99
|
|
100
|
-
|
101
|
-
|
100
|
+
attributes
|
101
|
+
end
|
102
102
|
|
103
|
-
|
104
|
-
|
105
|
-
|
103
|
+
def type_cast_attribute_value(multiparameter_name, value)
|
104
|
+
multiparameter_name =~ /\([0-9]*([if])\)/ ? value.send("to_" + $1) : value
|
105
|
+
end
|
106
106
|
|
107
|
-
|
108
|
-
|
109
|
-
|
107
|
+
def find_parameter_position(multiparameter_name)
|
108
|
+
multiparameter_name.scan(/\(([0-9]*).*\)/).first.first.to_i
|
109
|
+
end
|
110
110
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
111
|
+
def _assign_attribute(k, v, force_write_readonly: false)
|
112
|
+
if respond_to?("#{k}=")
|
113
|
+
public_send("#{k}=", v, force_write_readonly: force_write_readonly)
|
114
|
+
else
|
115
|
+
raise UnknownAttributeError.new(self, k)
|
116
|
+
end
|
116
117
|
end
|
117
|
-
end
|
118
118
|
end
|
119
119
|
end
|
@@ -27,8 +27,8 @@ module DuckRecord
|
|
27
27
|
extend ActiveSupport::Concern
|
28
28
|
|
29
29
|
included do
|
30
|
-
attribute_method_suffix
|
31
|
-
attribute_method_suffix
|
30
|
+
attribute_method_suffix "_before_type_cast"
|
31
|
+
attribute_method_suffix "_came_from_user?"
|
32
32
|
end
|
33
33
|
|
34
34
|
# Returns the value of the attribute identified by +attr_name+ before
|
@@ -63,14 +63,14 @@ module DuckRecord
|
|
63
63
|
|
64
64
|
private
|
65
65
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
66
|
+
# Handle *_before_type_cast for method_missing.
|
67
|
+
def attribute_before_type_cast(attribute_name)
|
68
|
+
read_attribute_before_type_cast(attribute_name)
|
69
|
+
end
|
70
70
|
|
71
|
-
|
72
|
-
|
73
|
-
|
71
|
+
def attribute_came_from_user?(attribute_name)
|
72
|
+
@attributes[attribute_name].came_from_user?
|
73
|
+
end
|
74
74
|
end
|
75
75
|
end
|
76
76
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
require
|
3
|
-
require
|
2
|
+
require "active_support/core_ext/module/attribute_accessors"
|
3
|
+
require "duck_record/attribute_mutation_tracker"
|
4
4
|
|
5
5
|
module DuckRecord
|
6
6
|
module AttributeMethods
|
@@ -68,40 +68,40 @@ module DuckRecord
|
|
68
68
|
|
69
69
|
private
|
70
70
|
|
71
|
-
|
72
|
-
|
73
|
-
|
71
|
+
def mutation_tracker
|
72
|
+
unless defined?(@mutation_tracker)
|
73
|
+
@mutation_tracker = nil
|
74
|
+
end
|
75
|
+
@mutation_tracker ||= AttributeMutationTracker.new(@attributes)
|
74
76
|
end
|
75
|
-
@mutation_tracker ||= AttributeMutationTracker.new(@attributes)
|
76
|
-
end
|
77
77
|
|
78
|
-
|
79
|
-
|
80
|
-
|
78
|
+
def changes_include?(attr_name)
|
79
|
+
super || mutation_tracker.changed?(attr_name)
|
80
|
+
end
|
81
81
|
|
82
|
-
|
83
|
-
|
84
|
-
|
82
|
+
def clear_attribute_change(attr_name)
|
83
|
+
mutation_tracker.forget_change(attr_name)
|
84
|
+
end
|
85
85
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
86
|
+
def store_original_attributes
|
87
|
+
@attributes = @attributes.map(&:forgetting_assignment)
|
88
|
+
@mutation_tracker = nil
|
89
|
+
end
|
90
90
|
|
91
|
-
|
92
|
-
|
93
|
-
|
91
|
+
def previous_mutation_tracker
|
92
|
+
@previous_mutation_tracker ||= NullMutationTracker.instance
|
93
|
+
end
|
94
94
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
95
|
+
def cache_changed_attributes
|
96
|
+
@cached_changed_attributes = changed_attributes
|
97
|
+
yield
|
98
|
+
ensure
|
99
|
+
clear_changed_attributes_cache
|
100
|
+
end
|
101
101
|
|
102
|
-
|
103
|
-
|
104
|
-
|
102
|
+
def clear_changed_attributes_cache
|
103
|
+
remove_instance_variable(:@cached_changed_attributes) if defined?(@cached_changed_attributes)
|
104
|
+
end
|
105
105
|
end
|
106
106
|
end
|
107
107
|
end
|