提交 2f9d8895 编写于 作者: S Sean Griffin

Persist user provided default values, even if unchanged

This is a usability change to fix a quirk from our definition of partial
writes. By default, we only persist changed attributes. When creating a
new record, this is assumed that the default values came from the
database. However, if the user provided a default, it will not be
persisted, since we didn't see it as "changed". Since this is a very
specific case, I wanted to isolate it with the other quirks that come
from user provided default values. The number of edge cases which are
presenting themselves are starting to make me wonder if we should just
remove the ability to assign a default, in favor of overriding
`initialize`. For the time being, this is required for the attributes
API to not have confusing behavior.

We had to delete one test, since this actually changes the meaning of
`.changed?` on Active Record models. It now specifically means
`changed_from_database?`. While I think this will make the attributes
API more ergonomic to use, it is a subtle change in definition (though
not a backwards incompatible one). We should probably figure out the
right place to document this. (Feel free to open a PR doing that if
you're reading this).

/cc @rafaelfranca @kirs @senny

This is an alternate implementation of #19921.

Close #19921.

[Sean Griffin & Kir Shatrov]
上级 a6e3cdae
......@@ -3,8 +3,9 @@
module ActiveRecord
class Attribute # :nodoc:
class UserProvidedDefault < FromUser
def initialize(name, value, type)
def initialize(name, value, type, database_default)
super(name, value, type)
@database_default = database_default
end
def type_cast(value)
......@@ -14,6 +15,18 @@ def type_cast(value)
super
end
end
def changed_in_place_from?(old_value)
super || changed_from?(database_default.value)
end
def with_type(type)
self.class.new(name, value_before_type_cast, type, database_default)
end
protected
attr_reader :database_default
end
end
end
......@@ -242,6 +242,7 @@ def define_default_attribute(name, value, type, from_user:)
name,
value,
type,
_default_attributes[name],
)
else
default_attribute = Attribute.from_database(name, value, type)
......
......@@ -135,6 +135,12 @@ def deserialize(*)
assert_equal 2, klass.new.counter
end
test "user provided defaults are persisted even if unchanged" do
model = OverloadedType.create!
assert_equal "the overloaded default", model.reload.string_with_default
end
if current_adapter?(:PostgreSQLAdapter)
test "arrays types can be specified" do
klass = Class.new(OverloadedType) do
......
......@@ -623,32 +623,6 @@ def test_datetime_attribute_doesnt_change_if_zone_is_modified_in_string
end
end
test "defaults with type that implements `serialize`" do
type = Class.new(ActiveRecord::Type::Value) do
def cast(value)
value.to_i
end
def serialize(value)
value.to_s
end
end
model_class = Class.new(ActiveRecord::Base) do
self.table_name = 'numeric_data'
attribute :foo, type.new, default: 1
end
model = model_class.new
assert_not model.foo_changed?
model = model_class.new(foo: 1)
assert_not model.foo_changed?
model = model_class.new(foo: '1')
assert_not model.foo_changed?
end
test "in place mutation detection" do
pirate = Pirate.create!(catchphrase: "arrrr")
pirate.catchphrase << " matey!"
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册