Make a deep copy of the _default_attributes in column_defaults

When column_defaults is called it calls `value` on each instance of
Attribute inside the _default_attributes set. Since value is memoized in
the Attribute instance and that Attribute instance is shared across all
instances of a model the next call to the default value will be memozied
not running the proc defined by the user.

Fixes #33031.
This commit is contained in:
Rafael Mendonça França 2018-09-19 23:58:43 -04:00
parent e184d1a94e
commit a0482d3911
No known key found for this signature in database
GPG Key ID: FC23B6D0F1EEE948
2 changed files with 15 additions and 1 deletions

@ -375,7 +375,7 @@ def type_for_attribute(attr_name, &block)
# default values when instantiating the Active Record object for this table.
def column_defaults
load_schema
@column_defaults ||= _default_attributes.to_hash
@column_defaults ||= _default_attributes.deep_dup.to_hash
end
def _default_attributes # :nodoc:

@ -148,6 +148,20 @@ def deserialize(*)
assert_equal 2, klass.new.counter
end
test "procs for default values are evaluated even after column_defaults is called" do
klass = Class.new(OverloadedType) do
@@counter = 0
attribute :counter, :integer, default: -> { @@counter += 1 }
end
assert_equal 1, klass.new.counter
# column_defaults will increment the counter since the proc is called
klass.column_defaults
assert_equal 3, klass.new.counter
end
test "procs are memoized before type casting" do
klass = Class.new(OverloadedType) do
@@counter = 0