diff options
author | Sean Griffin <sean@seantheprogrammer.com> | 2016-07-25 11:06:04 -0400 |
---|---|---|
committer | Sean Griffin <sean@seantheprogrammer.com> | 2016-07-25 11:12:06 -0400 |
commit | f0ddf87e4bfcfcb861b0a9dca32edb733668bd30 (patch) | |
tree | 37e30078178423e3b7f8850ab7b667f70db9abfc /activerecord/lib/active_record/attribute_set | |
parent | 7ebef567ce30d848dd8f9374fd70699fd91acb29 (diff) | |
download | rails-f0ddf87e4bfcfcb861b0a9dca32edb733668bd30.tar.gz rails-f0ddf87e4bfcfcb861b0a9dca32edb733668bd30.tar.bz2 rails-f0ddf87e4bfcfcb861b0a9dca32edb733668bd30.zip |
Correct the behavior of virtual attributes on models loaded from the db
Previously we had primarily tested the behavior of these attributes by
calling `.new`, allowing this to slip through the cracks. There were a
few ways in which they were behaving incorrectly.
The biggest issue was that attempting to read the attribute would
through a `MissingAttribute` error. We've corrected this by returning
the default value when the attribute isn't backed by a database column.
This is super special cased, but I don't see a way to avoid this
conditional. I had considered handling this higher up in
`define_default_attribute`, but we don't have the relevant information
there as users can provide new defaults for database columns as well.
Once I corrected this, I had noticed that the attributes were always
being marked as changed. This is because the behavior of
`define_default_attribute` was treating them as assigned from
`Attribute::Null`.
Finally, with our new implementation, `LazyAttributeHash` could no
longer be marshalled, as it holds onto a proc. This has been corrected
as well. I've not handled YAML in that class, as we do additional work
higher up to avoid YAML dumping it at all.
Fixes #25787
Close #25841
Diffstat (limited to 'activerecord/lib/active_record/attribute_set')
-rw-r--r-- | activerecord/lib/active_record/attribute_set/builder.rb | 28 |
1 files changed, 21 insertions, 7 deletions
diff --git a/activerecord/lib/active_record/attribute_set/builder.rb b/activerecord/lib/active_record/attribute_set/builder.rb index 24a255efc1..4ffd39d82d 100644 --- a/activerecord/lib/active_record/attribute_set/builder.rb +++ b/activerecord/lib/active_record/attribute_set/builder.rb @@ -3,11 +3,12 @@ require 'active_record/attribute' module ActiveRecord class AttributeSet # :nodoc: class Builder # :nodoc: - attr_reader :types, :always_initialized + attr_reader :types, :always_initialized, :default - def initialize(types, always_initialized = nil) + def initialize(types, always_initialized = nil, &default) @types = types @always_initialized = always_initialized + @default = default end def build_from_database(values = {}, additional_types = {}) @@ -15,21 +16,22 @@ module ActiveRecord values[always_initialized] = nil end - attributes = LazyAttributeHash.new(types, values, additional_types) + attributes = LazyAttributeHash.new(types, values, additional_types, &default) AttributeSet.new(attributes) end end end class LazyAttributeHash # :nodoc: - delegate :transform_values, :each_key, :each_value, to: :materialize + delegate :transform_values, :each_key, :each_value, :fetch, to: :materialize - def initialize(types, values, additional_types) + def initialize(types, values, additional_types, &default) @types = types @values = values @additional_types = additional_types @materialized = false @delegate_hash = {} + @default = default || proc {} end def key?(key) @@ -76,9 +78,21 @@ module ActiveRecord end end + def marshal_dump + materialize + end + + def marshal_load(delegate_hash) + @delegate_hash = delegate_hash + @types = {} + @values = {} + @additional_types = {} + @materialized = true + end + protected - attr_reader :types, :values, :additional_types, :delegate_hash + attr_reader :types, :values, :additional_types, :delegate_hash, :default def materialize unless @materialized @@ -101,7 +115,7 @@ module ActiveRecord if value_present delegate_hash[name] = Attribute.from_database(name, value, type) elsif types.key?(name) - delegate_hash[name] = Attribute.uninitialized(name, type) + delegate_hash[name] = default.call(name) || Attribute.uninitialized(name, type) end end end |