Class: Sequel::Model::Associations::EagerGraphLoader

Inherits:
Object
  • Object
show all
Defined in:
lib/sequel/model/associations.rb

Overview

This class is the internal implementation of eager_graph. It is responsible for taking an array of plain hashes and returning an array of model objects with all eager_graphed associations already set in the association cache.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(dataset) ⇒ EagerGraphLoader

Initialize all of the data structures used during loading.



2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
# File 'lib/sequel/model/associations.rb', line 2075

def initialize(dataset)
  opts = dataset.opts
  eager_graph = opts[:eager_graph]
  @master =  eager_graph[:master]
  requirements = eager_graph[:requirements]
  reflection_map = @reflection_map = eager_graph[:reflections]
  reciprocal_map = @reciprocal_map = eager_graph[:reciprocals]
  @unique = eager_graph[:cartesian_product_number] > 1
      
  alias_map = @alias_map = {}
  type_map = @type_map = {}
  after_load_map = @after_load_map = {}
  limit_map = @limit_map = {}
  reflection_map.each do |k, v|
    alias_map[k] = v[:name]
    type_map[k] = v.returns_array?
    after_load_map[k] = v[:after_load] unless v[:after_load].empty?
    limit_map[k] = v.limit_and_offset if v[:limit]
  end

  # Make dependency map hash out of requirements array for each association.
  # This builds a tree of dependencies that will be used for recursion
  # to ensure that all parts of the object graph are loaded into the
  # appropriate subordinate association.
  @dependency_map = {}
  # Sort the associations by requirements length, so that
  # requirements are added to the dependency hash before their
  # dependencies.
  requirements.sort_by{|a| a[1].length}.each do |ta, deps|
    if deps.empty?
      dependency_map[ta] = {}
    else
      deps = deps.dup
      hash = dependency_map[deps.shift]
      deps.each do |dep|
        hash = hash[dep]
      end
      hash[ta] = {}
    end
  end
      
  # This mapping is used to make sure that duplicate entries in the
  # result set are mapped to a single record.  For example, using a
  # single one_to_many association with 10 associated records,
  # the main object column values appear in the object graph 10 times.
  # We map by primary key, if available, or by the object's entire values,
  # if not. The mapping must be per table, so create sub maps for each table
  # alias.
  records_map = {@master=>{}}
  alias_map.keys.each{|ta| records_map[ta] = {}}
  @records_map = records_map

  datasets = opts[:graph][:table_aliases].to_a.reject{|ta,ds| ds.nil?}
  column_aliases = opts[:graph_aliases] || opts[:graph][:column_aliases]
  primary_keys = {}
  column_maps = {}
  models = {}
  row_procs = {}
  datasets.each do |ta, ds|
    models[ta] = ds.model
    primary_keys[ta] = []
    column_maps[ta] = {}
    row_procs[ta] = ds.row_proc
  end
  column_aliases.each do |col_alias, tc|
    ta, column = tc
    column_maps[ta][col_alias] = column
  end
  column_maps.each do |ta, h|
    pk = models[ta].primary_key
    if pk.is_a?(Array)
      primary_keys[ta] = []
      h.select{|ca, c| primary_keys[ta] << ca if pk.include?(c)}
    else
      h.select{|ca, c| primary_keys[ta] = ca if pk == c}
    end
  end
  @column_maps = column_maps
  @primary_keys = primary_keys
  @row_procs = row_procs

  # For performance, create two special maps for the master table,
  # so you can skip a hash lookup.
  @master_column_map = column_maps[master]
  @master_primary_keys = primary_keys[master]

  # Add a special hash mapping table alias symbols to 5 element arrays that just
  # contain the data in other data structures for that table alias.  This is
  # used for performance, to get all values in one hash lookup instead of
  # separate hash lookups for each data structure.
  ta_map = {}
  alias_map.keys.each do |ta|
    ta_map[ta] = [records_map[ta], row_procs[ta], alias_map[ta], type_map[ta], reciprocal_map[ta]]
  end
  @ta_map = ta_map
end

Instance Attribute Details

#after_load_mapObject (readonly)

Hash with table alias symbol keys and after_load hook values



2032
2033
2034
# File 'lib/sequel/model/associations.rb', line 2032

def after_load_map
  @after_load_map
end

#alias_mapObject (readonly)

Hash with table alias symbol keys and association name values



2035
2036
2037
# File 'lib/sequel/model/associations.rb', line 2035

def alias_map
  @alias_map
end

#column_mapsObject (readonly)

Hash with table alias symbol keys and subhash values mapping column_alias symbols to the symbol of the real name of the column



2039
2040
2041
# File 'lib/sequel/model/associations.rb', line 2039

def column_maps
  @column_maps
end

#dependency_mapObject (readonly)

Recursive hash with table alias symbol keys mapping to hashes with dependent table alias symbol keys.



2042
2043
2044
# File 'lib/sequel/model/associations.rb', line 2042

def dependency_map
  @dependency_map
end

#limit_mapObject (readonly)

Hash with table alias symbol keys and [limit, offset] values



2045
2046
2047
# File 'lib/sequel/model/associations.rb', line 2045

def limit_map
  @limit_map
end

#masterObject (readonly)

Hash with table alias symbol keys and callable values used to create model instances The table alias symbol for the primary model



2049
2050
2051
# File 'lib/sequel/model/associations.rb', line 2049

def master
  @master
end

#primary_keysObject (readonly)

Hash with table alias symbol keys and primary key symbol values (or arrays of primary key symbols for composite key tables)



2053
2054
2055
# File 'lib/sequel/model/associations.rb', line 2053

def primary_keys
  @primary_keys
end

#reciprocal_mapObject (readonly)

Hash with table alias symbol keys and reciprocal association symbol values, used for setting reciprocals for one_to_many associations.



2057
2058
2059
# File 'lib/sequel/model/associations.rb', line 2057

def reciprocal_map
  @reciprocal_map
end

#records_mapObject (readonly)

Hash with table alias symbol keys and subhash values mapping primary key symbols (or array of symbols) to model instances. Used so that only a single model instance is created for each object.



2061
2062
2063
# File 'lib/sequel/model/associations.rb', line 2061

def records_map
  @records_map
end

#reflection_mapObject (readonly)

Hash with table alias symbol keys and AssociationReflection values



2064
2065
2066
# File 'lib/sequel/model/associations.rb', line 2064

def reflection_map
  @reflection_map
end

#row_procsObject (readonly)

Hash with table alias symbol keys and callable values used to create model instances



2067
2068
2069
# File 'lib/sequel/model/associations.rb', line 2067

def row_procs
  @row_procs
end

#type_mapObject (readonly)

Hash with table alias symbol keys and true/false values, where true means the association represented by the table alias uses an array of values instead of a single value (i.e. true => *_many, false => *_to_one).



2072
2073
2074
# File 'lib/sequel/model/associations.rb', line 2072

def type_map
  @type_map
end

Instance Method Details

#load(hashes) ⇒ Object

Return an array of primary model instances with the associations cache prepopulated for all model objects (both primary and associated).



2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
# File 'lib/sequel/model/associations.rb', line 2174

def load(hashes)
  master = master()
      
  # Assign to local variables for speed increase
  rp = row_procs[master]
  rm = records_map[master]
  dm = dependency_map

  # This will hold the final record set that we will be replacing the object graph with.
  records = []

  hashes.each do |h|
    unless key = master_pk(h)
      key = hkey(master_hfor(h))
    end
    unless primary_record = rm[key]
      primary_record = rm[key] = rp.call(master_hfor(h))
      # Only add it to the list of records to return if it is a new record
      records.push(primary_record)
    end
    # Build all associations for the current object and it's dependencies
    _load(dm, primary_record, h)
  end
      
  # Remove duplicate records from all associations if this graph could possibly be a cartesian product
  # Run after_load procs if there are any
  post_process(records, dm) if @unique || !after_load_map.empty? || !limit_map.empty?

  records
end