class Sequel::Model::Associations::EagerGraphLoader

  1. lib/sequel/model/associations.rb
Superclass: Object

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.

Attributes

after_load_map [R]

Hash with table alias symbol keys and after_load hook values

alias_map [R]

Hash with table alias symbol keys and association name values

column_maps [R]

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

dependency_map [R]

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

limit_map [R]

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

master [R]

The table alias symbol for the primary model

primary_keys [R]

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

reciprocal_map [R]

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

records_map [R]

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.

reflection_map [R]

Hash with table alias symbol keys and AssociationReflection values

row_procs [R]

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

type_map [R]

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).

Public Class methods

new(dataset)

Initialize all of the data structures used during loading.

[show source]
     # File lib/sequel/model/associations.rb
3968 def initialize(dataset)
3969   opts = dataset.opts
3970   eager_graph = opts[:eager_graph]
3971   @master =  eager_graph[:master]
3972   requirements = eager_graph[:requirements]
3973   reflection_map = @reflection_map = eager_graph[:reflections]
3974   reciprocal_map = @reciprocal_map = eager_graph[:reciprocals]
3975   limit_map = @limit_map = eager_graph[:limits]
3976   @unique = eager_graph[:cartesian_product_number] > 1
3977       
3978   alias_map = @alias_map = {}
3979   type_map = @type_map = {}
3980   after_load_map = @after_load_map = {}
3981   reflection_map.each do |k, v|
3982     alias_map[k] = v[:name]
3983     after_load_map[k] = v[:after_load] if v[:after_load]
3984     type_map[k] = if v.returns_array?
3985       true
3986     elsif (limit_and_offset = limit_map[k]) && !limit_and_offset.last.nil?
3987       :offset
3988     end
3989   end
3990   after_load_map.freeze
3991   alias_map.freeze
3992   type_map.freeze
3993 
3994   # Make dependency map hash out of requirements array for each association.
3995   # This builds a tree of dependencies that will be used for recursion
3996   # to ensure that all parts of the object graph are loaded into the
3997   # appropriate subordinate association.
3998   dependency_map = @dependency_map = {}
3999   # Sort the associations by requirements length, so that
4000   # requirements are added to the dependency hash before their
4001   # dependencies.
4002   requirements.sort_by{|a| a[1].length}.each do |ta, deps|
4003     if deps.empty?
4004       dependency_map[ta] = {}
4005     else
4006       deps = deps.dup
4007       hash = dependency_map[deps.shift]
4008       deps.each do |dep|
4009         hash = hash[dep]
4010       end
4011       hash[ta] = {}
4012     end
4013   end
4014   freezer = lambda do |h|
4015     h.freeze
4016     h.each_value(&freezer)
4017   end
4018   freezer.call(dependency_map)
4019       
4020   datasets = opts[:graph][:table_aliases].to_a.reject{|ta,ds| ds.nil?}
4021   column_aliases = opts[:graph][:column_aliases]
4022   primary_keys = {}
4023   column_maps = {}
4024   models = {}
4025   row_procs = {}
4026   datasets.each do |ta, ds|
4027     models[ta] = ds.model
4028     primary_keys[ta] = []
4029     column_maps[ta] = {}
4030     row_procs[ta] = ds.row_proc
4031   end
4032   column_aliases.each do |col_alias, tc|
4033     ta, column = tc
4034     column_maps[ta][col_alias] = column
4035   end
4036   column_maps.each do |ta, h|
4037     pk = models[ta].primary_key
4038     if pk.is_a?(Array)
4039       primary_keys[ta] = []
4040       h.select{|ca, c| primary_keys[ta] << ca if pk.include?(c)}
4041     else
4042       h.select{|ca, c| primary_keys[ta] = ca if pk == c}
4043     end
4044   end
4045   @column_maps = column_maps.freeze
4046   @primary_keys = primary_keys.freeze
4047   @row_procs = row_procs.freeze
4048 
4049   # For performance, create two special maps for the master table,
4050   # so you can skip a hash lookup.
4051   @master_column_map = column_maps[master]
4052   @master_primary_keys = primary_keys[master]
4053 
4054   # Add a special hash mapping table alias symbols to 5 element arrays that just
4055   # contain the data in other data structures for that table alias.  This is
4056   # used for performance, to get all values in one hash lookup instead of
4057   # separate hash lookups for each data structure.
4058   ta_map = {}
4059   alias_map.each_key do |ta|
4060     ta_map[ta] = [row_procs[ta], alias_map[ta], type_map[ta], reciprocal_map[ta]].freeze
4061   end
4062   @ta_map = ta_map.freeze
4063   freeze
4064 end

Public Instance methods

load(hashes)

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

[show source]
     # File lib/sequel/model/associations.rb
4068 def load(hashes)
4069   # This mapping is used to make sure that duplicate entries in the
4070   # result set are mapped to a single record.  For example, using a
4071   # single one_to_many association with 10 associated records,
4072   # the main object column values appear in the object graph 10 times.
4073   # We map by primary key, if available, or by the object's entire values,
4074   # if not. The mapping must be per table, so create sub maps for each table
4075   # alias.
4076   @records_map = records_map = {}
4077   alias_map.keys.each{|ta| records_map[ta] = {}}
4078 
4079   master = master()
4080       
4081   # Assign to local variables for speed increase
4082   rp = row_procs[master]
4083   rm = records_map[master] = {}
4084   dm = dependency_map
4085 
4086   records_map.freeze
4087 
4088   # This will hold the final record set that we will be replacing the object graph with.
4089   records = []
4090 
4091   hashes.each do |h|
4092     unless key = master_pk(h)
4093       key = hkey(master_hfor(h))
4094     end
4095     unless primary_record = rm[key]
4096       primary_record = rm[key] = rp.call(master_hfor(h))
4097       # Only add it to the list of records to return if it is a new record
4098       records.push(primary_record)
4099     end
4100     # Build all associations for the current object and it's dependencies
4101     _load(dm, primary_record, h)
4102   end
4103       
4104   # Remove duplicate records from all associations if this graph could possibly be a cartesian product
4105   # Run after_load procs if there are any
4106   post_process(records, dm) if @unique || !after_load_map.empty? || !limit_map.empty?
4107 
4108   records_map.each_value(&:freeze)
4109   freeze
4110 
4111   records
4112 end