aboutsummaryrefslogtreecommitdiffstats
path: root/activemodel/lib/active_model/attribute_set/builder.rb
diff options
context:
space:
mode:
Diffstat (limited to 'activemodel/lib/active_model/attribute_set/builder.rb')
-rw-r--r--activemodel/lib/active_model/attribute_set/builder.rb124
1 files changed, 124 insertions, 0 deletions
diff --git a/activemodel/lib/active_model/attribute_set/builder.rb b/activemodel/lib/active_model/attribute_set/builder.rb
new file mode 100644
index 0000000000..2b1c2206ec
--- /dev/null
+++ b/activemodel/lib/active_model/attribute_set/builder.rb
@@ -0,0 +1,124 @@
+# frozen_string_literal: true
+
+require "active_model/attribute"
+
+module ActiveModel
+ class AttributeSet # :nodoc:
+ class Builder # :nodoc:
+ attr_reader :types, :default_attributes
+
+ def initialize(types, default_attributes = {})
+ @types = types
+ @default_attributes = default_attributes
+ end
+
+ def build_from_database(values = {}, additional_types = {})
+ attributes = LazyAttributeHash.new(types, values, additional_types, default_attributes)
+ AttributeSet.new(attributes)
+ end
+ end
+ end
+
+ class LazyAttributeHash # :nodoc:
+ delegate :transform_values, :each_key, :each_value, :fetch, :except, to: :materialize
+
+ def initialize(types, values, additional_types, default_attributes, delegate_hash = {})
+ @types = types
+ @values = values
+ @additional_types = additional_types
+ @materialized = false
+ @delegate_hash = delegate_hash
+ @default_attributes = default_attributes
+ 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 deep_dup
+ dup.tap do |copy|
+ copy.instance_variable_set(:@delegate_hash, delegate_hash.transform_values(&:dup))
+ end
+ end
+
+ def initialize_dup(_)
+ @delegate_hash = Hash[delegate_hash]
+ super
+ end
+
+ def select
+ keys = types.keys | values.keys | delegate_hash.keys
+ keys.each_with_object({}) do |key, hash|
+ attribute = self[key]
+ if yield(key, attribute)
+ hash[key] = attribute
+ end
+ end
+ end
+
+ def ==(other)
+ if other.is_a?(LazyAttributeHash)
+ materialize == other.materialize
+ else
+ materialize == other
+ end
+ end
+
+ def marshal_dump
+ [@types, @values, @additional_types, @default_attributes, @delegate_hash]
+ end
+
+ def marshal_load(values)
+ if values.is_a?(Hash)
+ empty_hash = {}.freeze
+ initialize(empty_hash, empty_hash, empty_hash, empty_hash, values)
+ @materialized = true
+ else
+ initialize(*values)
+ end
+ end
+
+ protected
+ def materialize
+ unless @materialized
+ values.each_key { |key| self[key] }
+ types.each_key { |key| self[key] }
+ unless frozen?
+ @materialized = true
+ end
+ end
+ delegate_hash
+ end
+
+ private
+ attr_reader :types, :values, :additional_types, :delegate_hash, :default_attributes
+
+ 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)
+ attr = default_attributes[name]
+ if attr
+ delegate_hash[name] = attr.dup
+ else
+ delegate_hash[name] = Attribute.uninitialized(name, type)
+ end
+ end
+ end
+ end
+end