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
3785 def initialize(dataset)
3786   opts = dataset.opts
3787   eager_graph = opts[:eager_graph]
3788   @master =  eager_graph[:master]
3789   requirements = eager_graph[:requirements]
3790   reflection_map = @reflection_map = eager_graph[:reflections]
3791   reciprocal_map = @reciprocal_map = eager_graph[:reciprocals]
3792   limit_map = @limit_map = eager_graph[:limits]
3793   @unique = eager_graph[:cartesian_product_number] > 1
3794       
3795   alias_map = @alias_map = {}
3796   type_map = @type_map = {}
3797   after_load_map = @after_load_map = {}
3798   reflection_map.each do |k, v|
3799     alias_map[k] = v[:name]
3800     after_load_map[k] = v[:after_load] if v[:after_load]
3801     type_map[k] = if v.returns_array?
3802       true
3803     elsif (limit_and_offset = limit_map[k]) && !limit_and_offset.last.nil?
3804       :offset
3805     end
3806   end
3807   after_load_map.freeze
3808   alias_map.freeze
3809   type_map.freeze
3810 
3811   # Make dependency map hash out of requirements array for each association.
3812   # This builds a tree of dependencies that will be used for recursion
3813   # to ensure that all parts of the object graph are loaded into the
3814   # appropriate subordinate association.
3815   dependency_map = @dependency_map = {}
3816   # Sort the associations by requirements length, so that
3817   # requirements are added to the dependency hash before their
3818   # dependencies.
3819   requirements.sort_by{|a| a[1].length}.each do |ta, deps|
3820     if deps.empty?
3821       dependency_map[ta] = {}
3822     else
3823       deps = deps.dup
3824       hash = dependency_map[deps.shift]
3825       deps.each do |dep|
3826         hash = hash[dep]
3827       end
3828       hash[ta] = {}
3829     end
3830   end
3831   freezer = lambda do |h|
3832     h.freeze
3833     h.each_value(&freezer)
3834   end
3835   freezer.call(dependency_map)
3836       
3837   datasets = opts[:graph][:table_aliases].to_a.reject{|ta,ds| ds.nil?}
3838   column_aliases = opts[:graph][:column_aliases]
3839   primary_keys = {}
3840   column_maps = {}
3841   models = {}
3842   row_procs = {}
3843   datasets.each do |ta, ds|
3844     models[ta] = ds.model
3845     primary_keys[ta] = []
3846     column_maps[ta] = {}
3847     row_procs[ta] = ds.row_proc
3848   end
3849   column_aliases.each do |col_alias, tc|
3850     ta, column = tc
3851     column_maps[ta][col_alias] = column
3852   end
3853   column_maps.each do |ta, h|
3854     pk = models[ta].primary_key
3855     if pk.is_a?(Array)
3856       primary_keys[ta] = []
3857       h.select{|ca, c| primary_keys[ta] << ca if pk.include?(c)}
3858     else
3859       h.select{|ca, c| primary_keys[ta] = ca if pk == c}
3860     end
3861   end
3862   @column_maps = column_maps.freeze
3863   @primary_keys = primary_keys.freeze
3864   @row_procs = row_procs.freeze
3865 
3866   # For performance, create two special maps for the master table,
3867   # so you can skip a hash lookup.
3868   @master_column_map = column_maps[master]
3869   @master_primary_keys = primary_keys[master]
3870 
3871   # Add a special hash mapping table alias symbols to 5 element arrays that just
3872   # contain the data in other data structures for that table alias.  This is
3873   # used for performance, to get all values in one hash lookup instead of
3874   # separate hash lookups for each data structure.
3875   ta_map = {}
3876   alias_map.each_key do |ta|
3877     ta_map[ta] = [row_procs[ta], alias_map[ta], type_map[ta], reciprocal_map[ta]].freeze
3878   end
3879   @ta_map = ta_map.freeze
3880   freeze
3881 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
3885 def load(hashes)
3886   # This mapping is used to make sure that duplicate entries in the
3887   # result set are mapped to a single record.  For example, using a
3888   # single one_to_many association with 10 associated records,
3889   # the main object column values appear in the object graph 10 times.
3890   # We map by primary key, if available, or by the object's entire values,
3891   # if not. The mapping must be per table, so create sub maps for each table
3892   # alias.
3893   @records_map = records_map = {}
3894   alias_map.keys.each{|ta| records_map[ta] = {}}
3895 
3896   master = master()
3897       
3898   # Assign to local variables for speed increase
3899   rp = row_procs[master]
3900   rm = records_map[master] = {}
3901   dm = dependency_map
3902 
3903   records_map.freeze
3904 
3905   # This will hold the final record set that we will be replacing the object graph with.
3906   records = []
3907 
3908   hashes.each do |h|
3909     unless key = master_pk(h)
3910       key = hkey(master_hfor(h))
3911     end
3912     unless primary_record = rm[key]
3913       primary_record = rm[key] = rp.call(master_hfor(h))
3914       # Only add it to the list of records to return if it is a new record
3915       records.push(primary_record)
3916     end
3917     # Build all associations for the current object and it's dependencies
3918     _load(dm, primary_record, h)
3919   end
3920       
3921   # Remove duplicate records from all associations if this graph could possibly be a cartesian product
3922   # Run after_load procs if there are any
3923   post_process(records, dm) if @unique || !after_load_map.empty? || !limit_map.empty?
3924 
3925   records_map.each_value(&:freeze)
3926   freeze
3927 
3928   records
3929 end