aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record/attribute_set/builder.rb
blob: 60154a06471d9fe8abaefc6e349d0cf94d9b5b96 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
module ActiveRecord
  class AttributeSet # :nodoc:
    class Builder # :nodoc:
      attr_reader :types, :always_initialized

      def initialize(types, always_initialized = nil)
        @types = types
        @always_initialized = always_initialized
      end

      def build_from_database(values = {}, additional_types = {})
        if always_initialized && !values.key?(always_initialized)
          values[always_initialized] = nil
        end

        attributes = LazyAttributeHash.new(types, values, additional_types)
        AttributeSet.new(attributes)
      end
    end
  end

  class LazyAttributeHash
    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)
      if delegate_hash.key?(key)
        delegate_hash[key]
      else
        assign_default_value(key)
      end
    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

    protected

    attr_reader :types, :values, :additional_types, :delegate_hash

    private

    def assign_default_value(name)
      type = additional_types.fetch(name, types[name])

      if values.key?(name)
        delegate_hash[name] = Attribute.from_database(name, values[name], type)
      elsif types.key?(name)
        delegate_hash[name] = Attribute.uninitialized(name, type)
      end
    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