diff options
Diffstat (limited to 'activerecord/lib/active_record/attribute_set/builder.rb')
-rw-r--r-- | activerecord/lib/active_record/attribute_set/builder.rb | 96 |
1 files changed, 65 insertions, 31 deletions
diff --git a/activerecord/lib/active_record/attribute_set/builder.rb b/activerecord/lib/active_record/attribute_set/builder.rb index 0a62c68bfb..05138ae36d 100644 --- a/activerecord/lib/active_record/attribute_set/builder.rb +++ b/activerecord/lib/active_record/attribute_set/builder.rb @@ -1,50 +1,84 @@ module ActiveRecord class AttributeSet # :nodoc: class Builder # :nodoc: - attr_reader :types + attr_reader :types, :always_initialized - def initialize(types) + def initialize(types, always_initialized = nil) @types = types + @always_initialized = always_initialized end def build_from_database(values = {}, additional_types = {}) - build_from_database_pairs(values.keys, values.values, additional_types) - end + if always_initialized && !values.key?(always_initialized) + values[always_initialized] = nil + end - def build_from_database_pairs(columns, values, additional_types) - attributes = build_attributes_from_values(columns, values, additional_types) - add_uninitialized_attributes(attributes) + attributes = LazyAttributeHash.new(types, values, additional_types) AttributeSet.new(attributes) end + end + end - private - - def build_attributes_from_values(columns, values, additional_types) - # We are performing manual iteration here as this method is a performance - # hotspot - hash = {} - index = 0 - length = columns.length - - while index < length - name = columns[index] - value = values[index] - type = additional_types.fetch(name, types[name]) - hash[name] = Attribute.from_database(name, value, type) - index += 1 - end + class LazyAttributeHash # :nodoc: + delegate :select, :transform_values, to: :materialize + + def initialize(types, values, additional_types) + @types = types + @values = values + @additional_types = additional_types + @materialized = false + @delegate_hash = {} + end + + def key?(key) + delegate_hash.key?(key) || values.key?(key) || types.key?(key) + end + + def [](key) + delegate_hash[key] || assign_default_value(key) + end + + def []=(key, value) + if frozen? + raise RuntimeError, "Can't modify frozen hash" + end + delegate_hash[key] = value + end + + def initialized_keys + delegate_hash.keys | values.keys + end + + def initialize_dup(_) + @delegate_hash = delegate_hash.transform_values(&:dup) + super + end - hash + protected + + attr_reader :types, :values, :additional_types, :delegate_hash + + private + + def assign_default_value(name) + type = additional_types.fetch(name, types[name]) + value_present = true + value = values.fetch(name) { value_present = false } + + if value_present + delegate_hash[name] = Attribute.from_database(name, value, type) + elsif types.key?(name) + delegate_hash[name] = Attribute.uninitialized(name, type) end + end - def add_uninitialized_attributes(attributes) - types.each_key do |name| - next if attributes.key? name - type = types[name] - attributes[name] = - Attribute.uninitialized(name, type) - end + def materialize + unless @materialized + values.each_key { |key| self[key] } + types.each_key { |key| self[key] } + @materialized = true end + delegate_hash end end end |