class Sequel::Model::Associations::EagerGraphLoader

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

def _load(dependency_map, current, h)

Recursive method that creates associated model objects and associates them to the current model object.
def _load(dependency_map, current, h)
  dependency_map.each do |ta, deps|
    unless key = pk(ta, h)
      ta_h = hfor(ta, h)
      unless ta_h.values.any?
        assoc_name = alias_map[ta]
        unless (assoc = current.associations).has_key?(assoc_name)
          assoc[assoc_name] = type_map[ta] ? [] : nil
        end
        next
      end
      key = hkey(ta_h)
    end
    rm, rp, assoc_name, tm, rcm = @ta_map[ta]
    unless rec = rm[key]
      rec = rm[key] = rp.call(hfor(ta, h))
    end
    if tm
      unless (assoc = current.associations).has_key?(assoc_name)
        assoc[assoc_name] = []
      end
      assoc[assoc_name].push(rec) 
      rec.associations[rcm] = current if rcm
    else
      current.associations[assoc_name] ||= rec
    end
    # Recurse into dependencies of the current object
    _load(deps, rec, h) unless deps.empty?
  end
end

def hfor(ta, h)

Return the subhash for the specific table alias +ta+ by parsing the values out of the main hash +h+
def hfor(ta, h)
  out = {}
  @column_maps[ta].each{|ca, c| out[c] = h[ca]}
  out
end

def hkey(h)

This is only used if the primary key cannot be used.
Return a suitable hash key for any subhash +h+, which is an array of values by column order.
def hkey(h)
  h.sort_by{|x| x[0].to_s}
end

def initialize(dataset)

Initialize all of the data structures used during loading.
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

def load(hashes)

for all model objects (both primary and associated).
Return an array of primary model instances with the associations cache prepopulated
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

def master_hfor(h)

Return the subhash for the master table by parsing the values out of the main hash +h+
def master_hfor(h)
  out = {}
  @master_column_map.each{|ca, c| out[c] = h[ca]}
  out
end

def master_pk(h)

Return a primary key value for the master table by parsing it out of the main hash +h+.
def master_pk(h)
  x = @master_primary_keys
  if x.is_a?(Array)
    unless x == []
      x = x.map{|ca| h[ca]}
      x if x.all?
    end
  else
    h[x]
  end
end

def pk(ta, h)

Return a primary key value for the given table alias by parsing it out of the main hash +h+.
def pk(ta, h)
  x = primary_keys[ta]
  if x.is_a?(Array)
    unless x == []
      x = x.map{|ca| h[ca]}
      x if x.all?
    end
  else
    h[x]
  end
end

def post_process(records, dependency_map)

Note that this can cause legitimate duplicate records to be removed.
uniq! on the association to make sure no duplicate records show up.
In that case, for each object in all associations loaded via +eager_graph+, run
there are multiple records for each association when there should only be one.
If the result set is the result of a cartesian product, then it is possible that
def post_process(records, dependency_map)
  records.each do |record|
    dependency_map.each do |ta, deps|
      assoc_name = alias_map[ta]
      list = record.send(assoc_name)
      rec_list = if type_map[ta]
        list.uniq!
        if lo = limit_map[ta]
          limit, offset = lo
          list.replace(list[offset||0, limit])
        end
        list
      elsif list
        [list]
      else
        []
      end
      record.send(:run_association_callbacks, reflection_map[ta], :after_load, list) if after_load_map[ta]
      post_process(rec_list, deps) if !rec_list.empty? && !deps.empty?
    end
  end
end