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