Skip to content

Commit caf9413

Browse files
Merge pull request rails#46231 from jonathanhefner/active_model-memoize-value_for_database
Avoid unnecessary `serialize` calls after save
2 parents c2c7781 + 5e62c19 commit caf9413

File tree

3 files changed

+50
-6
lines changed

3 files changed

+50
-6
lines changed

activemodel/lib/active_model/attribute.rb

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,10 @@ def original_value
5353
end
5454

5555
def value_for_database
56-
type.serialize(value)
56+
if !defined?(@value_for_database) || type.changed_in_place?(@value_for_database, value)
57+
@value_for_database = _value_for_database
58+
end
59+
@value_for_database
5760
end
5861

5962
def serializable?(&block)
@@ -159,6 +162,10 @@ def changed_from_assignment?
159162
assigned? && type.changed?(original_value, value, value_before_type_cast)
160163
end
161164

165+
def _value_for_database
166+
type.serialize(value)
167+
end
168+
162169
def _original_value_for_database
163170
type.serialize(original_value)
164171
end
@@ -179,13 +186,14 @@ def type_cast(value)
179186
type.cast(value)
180187
end
181188

182-
def value_for_database
183-
Type::SerializeCastValue.serialize(type, value)
184-
end
185-
186189
def came_from_user?
187190
!type.value_constructed_by_mass_assignment?(value_before_type_cast)
188191
end
192+
193+
private
194+
def _value_for_database
195+
Type::SerializeCastValue.serialize(type, value)
196+
end
189197
end
190198

191199
class WithCastValue < Attribute # :nodoc:

activemodel/test/cases/attribute_test.rb

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
module ActiveModel
66
class AttributeTest < ActiveModel::TestCase
77
class InscribingType
8+
def changed_in_place?(raw_old_value, new_value)
9+
false
10+
end
11+
812
def cast(value)
913
"cast(#{value})"
1014
end
@@ -96,6 +100,37 @@ def serialize_cast_value(value)
96100
assert_equal "serialize_cast_value(cast(whatever))", attribute.value_for_database
97101
end
98102

103+
test "value_for_database is memoized" do
104+
count = 0
105+
@type.define_singleton_method(:serialize) do |value|
106+
count += 1
107+
nil
108+
end
109+
110+
attribute = Attribute.from_user(nil, "whatever", @type)
111+
112+
attribute.value_for_database
113+
attribute.value_for_database
114+
assert_equal 1, count
115+
end
116+
117+
test "value_for_database is recomputed when value changes in place" do
118+
count = 0
119+
@type.define_singleton_method(:serialize) do |value|
120+
count += 1
121+
nil
122+
end
123+
@type.define_singleton_method(:changed_in_place?) do |*|
124+
true
125+
end
126+
127+
attribute = Attribute.from_user(nil, "whatever", @type)
128+
129+
attribute.value_for_database
130+
attribute.value_for_database
131+
assert_equal 2, count
132+
end
133+
99134
test "duping dups the value" do
100135
attribute = Attribute.from_database(nil, "a value", @type)
101136

activerecord/lib/active_record/relation/query_attribute.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ def type_cast(value)
1010
end
1111

1212
def value_for_database
13-
@value_for_database ||= super
13+
@value_for_database = _value_for_database unless defined?(@value_for_database)
14+
@value_for_database
1415
end
1516

1617
def with_cast_value(value)

0 commit comments

Comments
 (0)