Module: Sequel::Packer::EagerHash

Defined in:
lib/sequel/packer/eager_hash.rb

Defined Under Namespace

Classes: MixedProcHashError, MultipleProcKeysError, NestedEagerProcsError

Class Method Summary collapse

Class Method Details

.deep_dup(hash) ⇒ Object

Creates a deep copy of an eager hash.



165
166
167
168
# File 'lib/sequel/packer/eager_hash.rb', line 165

def self.deep_dup(hash)
  return nil if !hash
  hash.transform_values {|val| deep_dup(val)}
end

.is_proc_hash?(hash) ⇒ Boolean

Returns:

  • (Boolean)


107
108
109
110
111
# File 'lib/sequel/packer/eager_hash.rb', line 107

def self.is_proc_hash?(hash)
  return false if !hash.is_a?(Hash)
  return false if hash.size != 1
  hash.keys[0].is_a?(Proc)
end

.merge(hash1, hash2) ⇒ Object

Merges two eager hashes together, without modifying either one.



114
115
116
117
# File 'lib/sequel/packer/eager_hash.rb', line 114

def self.merge(hash1, hash2)
  return deep_dup(hash2) if !hash1
  merge!(deep_dup(hash1), hash2)
end

.merge!(hash1, hash2) ⇒ Object

Merges two eager hashes together, modifying the first hash, while leaving the second unmodified. Since the first argument may be nil, callers should still use the return value, rather than the first argument.



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/sequel/packer/eager_hash.rb', line 123

def self.merge!(hash1, hash2)
  return hash1 if !hash2
  return deep_dup(hash2) if !hash1

  hash2.each do |key, val2|
    if !hash1.key?(key)
      hash1[key] = deep_dup(val2)
      next
    end

    val1 = hash1[key]
    h1_is_proc_hash = is_proc_hash?(val1)
    h2_is_proc_hash = is_proc_hash?(val2)

    case [h1_is_proc_hash, h2_is_proc_hash]
    when [false, false]
      hash1[key] = merge!(val1, val2)
    when [true, false]
      # We want to actually merge the hash the proc points to.
      eager_proc, nested_hash = val1.entries[0]
      hash1[key] = {eager_proc => merge!(nested_hash, val2)}
    when [false, true]
      # Same as above, but flipped. Notice the order of the arguments to
      # merge! to ensure hash2 is not modified!
      eager_proc, nested_hash = val2.entries[0]
      hash1[key] = {eager_proc => merge!(val1, nested_hash)}
    when [true, true]
      # Create a new proc that applies both procs, then merge their
      # respective hashes.
      proc1, nested_hash1 = val1.entries[0]
      proc2, nested_hash2 = val2.entries[0]

      new_proc = (proc {|ds| proc2.call(proc1.call(ds))})

      hash1[key] = {new_proc => merge!(nested_hash1, nested_hash2)}
    end
  end

  hash1
end

.normalize_eager_args(*associations) ⇒ Object

Sequel’s eager function can accept arguments in a number of different formats:

.eager(:assoc)
.eager([:assoc1, :assoc2])
.eager(assoc: :nested_assoc)
.eager(
  :assoc1,
  assoc2: {(proc {|ds| ...}) => [:nested_assoc1, :nested_assoc2]},
)

This method normalizes these arguments such that:

  • A Hash is returned

  • The keys of that hash are the names of associations

  • The values of that hash are either:

    • nil, representing no nested associations

    • a nested normalized hash, meeting these definitions

    • a “Proc hash”, which is a hash with a single key, which is a proc, and whose value is either nil or a nested normalized hash.

Notice that a normalized has the property that every value in the hash is either nil, or itself a normalized hash.

Note that this method cannot return a “Proc hash” as the top level normalized hash; Proc hashes can only be nested under other associations.



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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/sequel/packer/eager_hash.rb', line 40

def self.normalize_eager_args(*associations)
  # Implementation largely borrowed from Sequel's
  # eager_options_for_associations:
  #
  # https://github.com/jeremyevans/sequel/blob/5.32.0/lib/sequel/model/associations.rb#L3228-L3245
  normalized_hash = {}

  associations.flatten.each do |association|
    case association
    when Symbol
      normalized_hash[association] = nil
    when Hash
      num_proc_keys = 0
      num_symbol_keys = 0

      association.each do |key, val|
        case key
        when Symbol
          num_symbol_keys += 1
        when Proc
          num_proc_keys += 1

          if val.is_a?(Proc) || is_proc_hash?(val)
            raise(
              NestedEagerProcsError,
              "eager hash has nested Procs: #{associations.inspect}",
            )
          end
        end

        if val.nil?
          # Already normalized
          normalized_hash[key] = nil
        elsif val.is_a?(Proc)
          # Convert Proc value to a Proc hash.
          normalized_hash[key] = {val => nil}
        else
          # Otherwise recurse.
          normalized_hash[key] = normalize_eager_args(val)
        end
      end

      if num_proc_keys > 1
        raise(
          MultipleProcKeysError,
          "eager hash has multiple Proc keys: #{associations.inspect}",
        )
      end

      if num_proc_keys > 0 && num_symbol_keys > 0
        raise(
          MixedProcHashError,
          'eager hash has both symbol keys and Proc keys: ' +
            associations.inspect,
        )
      end
    else
      raise(
        Sequel::Error,
        'Associations must be in the form of a symbol or hash',
      )
    end
  end

  normalized_hash
end