| 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.
| 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). |
Initialize all of the data structures used during loading.
# File lib/sequel/model/associations.rb, line 3212
3212: def initialize(dataset)
3213: opts = dataset.opts
3214: eager_graph = opts[:eager_graph]
3215: @master = eager_graph[:master]
3216: requirements = eager_graph[:requirements]
3217: reflection_map = @reflection_map = eager_graph[:reflections]
3218: reciprocal_map = @reciprocal_map = eager_graph[:reciprocals]
3219: limit_map = @limit_map = eager_graph[:limits]
3220: @unique = eager_graph[:cartesian_product_number] > 1
3221:
3222: alias_map = @alias_map = {}
3223: type_map = @type_map = {}
3224: after_load_map = @after_load_map = {}
3225: reflection_map.each do |k, v|
3226: alias_map[k] = v[:name]
3227: after_load_map[k] = v[:after_load] if v[:after_load]
3228: type_map[k] = if v.returns_array?
3229: true
3230: elsif (limit_and_offset = limit_map[k]) && !limit_and_offset.last.nil?
3231: :offset
3232: end
3233: end
3234:
3235: # Make dependency map hash out of requirements array for each association.
3236: # This builds a tree of dependencies that will be used for recursion
3237: # to ensure that all parts of the object graph are loaded into the
3238: # appropriate subordinate association.
3239: @dependency_map = {}
3240: # Sort the associations by requirements length, so that
3241: # requirements are added to the dependency hash before their
3242: # dependencies.
3243: requirements.sort_by{|a| a[1].length}.each do |ta, deps|
3244: if deps.empty?
3245: dependency_map[ta] = {}
3246: else
3247: deps = deps.dup
3248: hash = dependency_map[deps.shift]
3249: deps.each do |dep|
3250: hash = hash[dep]
3251: end
3252: hash[ta] = {}
3253: end
3254: end
3255:
3256: # This mapping is used to make sure that duplicate entries in the
3257: # result set are mapped to a single record. For example, using a
3258: # single one_to_many association with 10 associated records,
3259: # the main object column values appear in the object graph 10 times.
3260: # We map by primary key, if available, or by the object's entire values,
3261: # if not. The mapping must be per table, so create sub maps for each table
3262: # alias.
3263: records_map = {@master=>{}}
3264: alias_map.keys.each{|ta| records_map[ta] = {}}
3265: @records_map = records_map
3266:
3267: datasets = opts[:graph][:table_aliases].to_a.reject{|ta,ds| ds.nil?}
3268: column_aliases = opts[:graph][:column_aliases]
3269: primary_keys = {}
3270: column_maps = {}
3271: models = {}
3272: row_procs = {}
3273: datasets.each do |ta, ds|
3274: models[ta] = ds.model
3275: primary_keys[ta] = []
3276: column_maps[ta] = {}
3277: row_procs[ta] = ds.row_proc
3278: end
3279: column_aliases.each do |col_alias, tc|
3280: ta, column = tc
3281: column_maps[ta][col_alias] = column
3282: end
3283: column_maps.each do |ta, h|
3284: pk = models[ta].primary_key
3285: if pk.is_a?(Array)
3286: primary_keys[ta] = []
3287: h.select{|ca, c| primary_keys[ta] << ca if pk.include?(c)}
3288: else
3289: h.select{|ca, c| primary_keys[ta] = ca if pk == c}
3290: end
3291: end
3292: @column_maps = column_maps
3293: @primary_keys = primary_keys
3294: @row_procs = row_procs
3295:
3296: # For performance, create two special maps for the master table,
3297: # so you can skip a hash lookup.
3298: @master_column_map = column_maps[master]
3299: @master_primary_keys = primary_keys[master]
3300:
3301: # Add a special hash mapping table alias symbols to 5 element arrays that just
3302: # contain the data in other data structures for that table alias. This is
3303: # used for performance, to get all values in one hash lookup instead of
3304: # separate hash lookups for each data structure.
3305: ta_map = {}
3306: alias_map.keys.each do |ta|
3307: ta_map[ta] = [records_map[ta], row_procs[ta], alias_map[ta], type_map[ta], reciprocal_map[ta]]
3308: end
3309: @ta_map = ta_map
3310: end
Return an array of primary model instances with the associations cache prepopulated for all model objects (both primary and associated).
# File lib/sequel/model/associations.rb, line 3314
3314: def load(hashes)
3315: master = master()
3316:
3317: # Assign to local variables for speed increase
3318: rp = row_procs[master]
3319: rm = records_map[master]
3320: dm = dependency_map
3321:
3322: # This will hold the final record set that we will be replacing the object graph with.
3323: records = []
3324:
3325: hashes.each do |h|
3326: unless key = master_pk(h)
3327: key = hkey(master_hfor(h))
3328: end
3329: unless primary_record = rm[key]
3330: primary_record = rm[key] = rp.call(master_hfor(h))
3331: # Only add it to the list of records to return if it is a new record
3332: records.push(primary_record)
3333: end
3334: # Build all associations for the current object and it's dependencies
3335: _load(dm, primary_record, h)
3336: end
3337:
3338: # Remove duplicate records from all associations if this graph could possibly be a cartesian product
3339: # Run after_load procs if there are any
3340: post_process(records, dm) if @unique || !after_load_map.empty? || !limit_map.empty?
3341:
3342: records
3343: end