Class Sequel::Model::Associations::EagerGraphLoader
In: lib/sequel/model/associations.rb
Parent: 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.

Methods

load   new  

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]  Hash with table alias symbol keys and callable values used to create model instances 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

Initialize all of the data structures used during loading.

[Source]

      # File lib/sequel/model/associations.rb, line 3206
3206:         def initialize(dataset)
3207:           opts = dataset.opts
3208:           eager_graph = opts[:eager_graph]
3209:           @master =  eager_graph[:master]
3210:           requirements = eager_graph[:requirements]
3211:           reflection_map = @reflection_map = eager_graph[:reflections]
3212:           reciprocal_map = @reciprocal_map = eager_graph[:reciprocals]
3213:           limit_map = @limit_map = eager_graph[:limits]
3214:           @unique = eager_graph[:cartesian_product_number] > 1
3215:       
3216:           alias_map = @alias_map = {}
3217:           type_map = @type_map = {}
3218:           after_load_map = @after_load_map = {}
3219:           reflection_map.each do |k, v|
3220:             alias_map[k] = v[:name]
3221:             after_load_map[k] = v[:after_load] if v[:after_load]
3222:             type_map[k] = if v.returns_array?
3223:               true
3224:             elsif (limit_and_offset = limit_map[k]) && !limit_and_offset.last.nil?
3225:               :offset
3226:             end
3227:           end
3228: 
3229:           # Make dependency map hash out of requirements array for each association.
3230:           # This builds a tree of dependencies that will be used for recursion
3231:           # to ensure that all parts of the object graph are loaded into the
3232:           # appropriate subordinate association.
3233:           @dependency_map = {}
3234:           # Sort the associations by requirements length, so that
3235:           # requirements are added to the dependency hash before their
3236:           # dependencies.
3237:           requirements.sort_by{|a| a[1].length}.each do |ta, deps|
3238:             if deps.empty?
3239:               dependency_map[ta] = {}
3240:             else
3241:               deps = deps.dup
3242:               hash = dependency_map[deps.shift]
3243:               deps.each do |dep|
3244:                 hash = hash[dep]
3245:               end
3246:               hash[ta] = {}
3247:             end
3248:           end
3249:       
3250:           # This mapping is used to make sure that duplicate entries in the
3251:           # result set are mapped to a single record.  For example, using a
3252:           # single one_to_many association with 10 associated records,
3253:           # the main object column values appear in the object graph 10 times.
3254:           # We map by primary key, if available, or by the object's entire values,
3255:           # if not. The mapping must be per table, so create sub maps for each table
3256:           # alias.
3257:           records_map = {@master=>{}}
3258:           alias_map.keys.each{|ta| records_map[ta] = {}}
3259:           @records_map = records_map
3260: 
3261:           datasets = opts[:graph][:table_aliases].to_a.reject{|ta,ds| ds.nil?}
3262:           column_aliases = opts[:graph][:column_aliases]
3263:           primary_keys = {}
3264:           column_maps = {}
3265:           models = {}
3266:           row_procs = {}
3267:           datasets.each do |ta, ds|
3268:             models[ta] = ds.model
3269:             primary_keys[ta] = []
3270:             column_maps[ta] = {}
3271:             row_procs[ta] = ds.row_proc
3272:           end
3273:           column_aliases.each do |col_alias, tc|
3274:             ta, column = tc
3275:             column_maps[ta][col_alias] = column
3276:           end
3277:           column_maps.each do |ta, h|
3278:             pk = models[ta].primary_key
3279:             if pk.is_a?(Array)
3280:               primary_keys[ta] = []
3281:               h.select{|ca, c| primary_keys[ta] << ca if pk.include?(c)}
3282:             else
3283:               h.select{|ca, c| primary_keys[ta] = ca if pk == c}
3284:             end
3285:           end
3286:           @column_maps = column_maps
3287:           @primary_keys = primary_keys
3288:           @row_procs = row_procs
3289: 
3290:           # For performance, create two special maps for the master table,
3291:           # so you can skip a hash lookup.
3292:           @master_column_map = column_maps[master]
3293:           @master_primary_keys = primary_keys[master]
3294: 
3295:           # Add a special hash mapping table alias symbols to 5 element arrays that just
3296:           # contain the data in other data structures for that table alias.  This is
3297:           # used for performance, to get all values in one hash lookup instead of
3298:           # separate hash lookups for each data structure.
3299:           ta_map = {}
3300:           alias_map.keys.each do |ta|
3301:             ta_map[ta] = [records_map[ta], row_procs[ta], alias_map[ta], type_map[ta], reciprocal_map[ta]]
3302:           end
3303:           @ta_map = ta_map
3304:         end

Public Instance methods

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

[Source]

      # File lib/sequel/model/associations.rb, line 3308
3308:         def load(hashes)
3309:           master = master()
3310:       
3311:           # Assign to local variables for speed increase
3312:           rp = row_procs[master]
3313:           rm = records_map[master]
3314:           dm = dependency_map
3315: 
3316:           # This will hold the final record set that we will be replacing the object graph with.
3317:           records = []
3318: 
3319:           hashes.each do |h|
3320:             unless key = master_pk(h)
3321:               key = hkey(master_hfor(h))
3322:             end
3323:             unless primary_record = rm[key]
3324:               primary_record = rm[key] = rp.call(master_hfor(h))
3325:               # Only add it to the list of records to return if it is a new record
3326:               records.push(primary_record)
3327:             end
3328:             # Build all associations for the current object and it's dependencies
3329:             _load(dm, primary_record, h)
3330:           end
3331:       
3332:           # Remove duplicate records from all associations if this graph could possibly be a cartesian product
3333:           # Run after_load procs if there are any
3334:           post_process(records, dm) if @unique || !after_load_map.empty? || !limit_map.empty?
3335: 
3336:           records
3337:         end

[Validate]