提交 ba06dab5 编写于 作者: S Sean Griffin

Memoize user provided defaults before type casting

When a proc is given as a default value, the form builder ends up
displaying `Proc#to_s` when the default is used. That's because we
didn't handle the proc until type casting. This issue technically can
occur any time that a proc is the value before type casting, but in
reality the only place that will occur is when a proc default is
provided through the attributes API, so the best place to handle this
edge case is there.

I've opted to memoize instead of just moving the `Proc#call` up, as this
made me realize that it could potentially interact very poorly with
dirty checking.

The code here is a little redundant, but I don't want to rely on how
`value_before_type_cast` is implemented in the super class, even if it's
just an `attr_reader`.

Fixes #24249

Close #24306
上级 133befb5
* The form builder now properly displays values when passing a proc form
default to the attributes API.
Fixes #24249.
*Sean Griffin*
* MySQL: strict mode respects other SQL modes rather than overwriting them.
Setting `strict: true` adds `STRICT_ALL_TABLES` to `sql_mode`. Setting
`strict: false` removes `STRICT_TRANS_TABLES`, `STRICT_ALL_TABLES`, and
......
......@@ -4,20 +4,25 @@ module ActiveRecord
class Attribute # :nodoc:
class UserProvidedDefault < FromUser # :nodoc:
def initialize(name, value, type, database_default)
@user_provided_value = value
super(name, value, type, database_default)
end
def type_cast(value)
if value.is_a?(Proc)
super(value.call)
def value_before_type_cast
if user_provided_value.is_a?(Proc)
@memoized_value_before_type_cast ||= user_provided_value.call
else
super
@user_provided_value
end
end
def with_type(type)
self.class.new(name, value_before_type_cast, type, original_attribute)
self.class.new(name, user_provided_value, type, original_attribute)
end
protected
attr_reader :user_provided_value
end
end
end
......@@ -135,6 +135,17 @@ def deserialize(*)
assert_equal 2, klass.new.counter
end
test "procs are memoized before type casting" do
klass = Class.new(OverloadedType) do
@@counter = 0
attribute :counter, :integer, default: -> { @@counter += 1 }
end
model = klass.new
assert_equal 1, model.counter_before_type_cast
assert_equal 1, model.counter_before_type_cast
end
test "user provided defaults are persisted even if unchanged" do
model = OverloadedType.create!
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册