Parent

Class Index [+]

Quicksearch

Sequel::Model::Associations::EagerGraphLoader

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]

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

new(dataset) click to toggle source

Initialize all of the data structures used during loading.

      # File lib/sequel/model/associations.rb, line 2132
2132:         def initialize(dataset)
2133:           opts = dataset.opts
2134:           eager_graph = opts[:eager_graph]
2135:           @master =  eager_graph[:master]
2136:           requirements = eager_graph[:requirements]
2137:           reflection_map = @reflection_map = eager_graph[:reflections]
2138:           reciprocal_map = @reciprocal_map = eager_graph[:reciprocals]
2139:           @unique = eager_graph[:cartesian_product_number] > 1
2140:       
2141:           alias_map = @alias_map = {}
2142:           type_map = @type_map = {}
2143:           after_load_map = @after_load_map = {}
2144:           limit_map = @limit_map = {}
2145:           reflection_map.each do |k, v|
2146:             alias_map[k] = v[:name]
2147:             type_map[k] = v.returns_array?
2148:             after_load_map[k] = v[:after_load] unless v[:after_load].empty?
2149:             limit_map[k] = v.limit_and_offset if v[:limit]
2150:           end
2151: 
2152:           # Make dependency map hash out of requirements array for each association.
2153:           # This builds a tree of dependencies that will be used for recursion
2154:           # to ensure that all parts of the object graph are loaded into the
2155:           # appropriate subordinate association.
2156:           @dependency_map = {}
2157:           # Sort the associations by requirements length, so that
2158:           # requirements are added to the dependency hash before their
2159:           # dependencies.
2160:           requirements.sort_by{|a| a[1].length}.each do |ta, deps|
2161:             if deps.empty?
2162:               dependency_map[ta] = {}
2163:             else
2164:               deps = deps.dup
2165:               hash = dependency_map[deps.shift]
2166:               deps.each do |dep|
2167:                 hash = hash[dep]
2168:               end
2169:               hash[ta] = {}
2170:             end
2171:           end
2172:       
2173:           # This mapping is used to make sure that duplicate entries in the
2174:           # result set are mapped to a single record.  For example, using a
2175:           # single one_to_many association with 10 associated records,
2176:           # the main object column values appear in the object graph 10 times.
2177:           # We map by primary key, if available, or by the object's entire values,
2178:           # if not. The mapping must be per table, so create sub maps for each table
2179:           # alias.
2180:           records_map = {@master=>{}}
2181:           alias_map.keys.each{|ta| records_map[ta] = {}}
2182:           @records_map = records_map
2183: 
2184:           datasets = opts[:graph][:table_aliases].to_a.reject{|ta,ds| ds.nil?}
2185:           column_aliases = opts[:graph_aliases] || opts[:graph][:column_aliases]
2186:           primary_keys = {}
2187:           column_maps = {}
2188:           models = {}
2189:           row_procs = {}
2190:           datasets.each do |ta, ds|
2191:             models[ta] = ds.model
2192:             primary_keys[ta] = []
2193:             column_maps[ta] = {}
2194:             row_procs[ta] = ds.row_proc
2195:           end
2196:           column_aliases.each do |col_alias, tc|
2197:             ta, column = tc
2198:             column_maps[ta][col_alias] = column
2199:           end
2200:           column_maps.each do |ta, h|
2201:             pk = models[ta].primary_key
2202:             if pk.is_a?(Array)
2203:               primary_keys[ta] = []
2204:               h.select{|ca, c| primary_keys[ta] << ca if pk.include?(c)}
2205:             else
2206:               h.select{|ca, c| primary_keys[ta] = ca if pk == c}
2207:             end
2208:           end
2209:           @column_maps = column_maps
2210:           @primary_keys = primary_keys
2211:           @row_procs = row_procs
2212: 
2213:           # For performance, create two special maps for the master table,
2214:           # so you can skip a hash lookup.
2215:           @master_column_map = column_maps[master]
2216:           @master_primary_keys = primary_keys[master]
2217: 
2218:           # Add a special hash mapping table alias symbols to 5 element arrays that just
2219:           # contain the data in other data structures for that table alias.  This is
2220:           # used for performance, to get all values in one hash lookup instead of
2221:           # separate hash lookups for each data structure.
2222:           ta_map = {}
2223:           alias_map.keys.each do |ta|
2224:             ta_map[ta] = [records_map[ta], row_procs[ta], alias_map[ta], type_map[ta], reciprocal_map[ta]]
2225:           end
2226:           @ta_map = ta_map
2227:         end

Public Instance Methods

load(hashes) click to toggle source

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 2231
2231:         def load(hashes)
2232:           master = master()
2233:       
2234:           # Assign to local variables for speed increase
2235:           rp = row_procs[master]
2236:           rm = records_map[master]
2237:           dm = dependency_map
2238: 
2239:           # This will hold the final record set that we will be replacing the object graph with.
2240:           records = []
2241: 
2242:           hashes.each do |h|
2243:             unless key = master_pk(h)
2244:               key = hkey(master_hfor(h))
2245:             end
2246:             unless primary_record = rm[key]
2247:               primary_record = rm[key] = rp.call(master_hfor(h))
2248:               # Only add it to the list of records to return if it is a new record
2249:               records.push(primary_record)
2250:             end
2251:             # Build all associations for the current object and it's dependencies
2252:             _load(dm, primary_record, h)
2253:           end
2254:       
2255:           # Remove duplicate records from all associations if this graph could possibly be a cartesian product
2256:           # Run after_load procs if there are any
2257:           post_process(records, dm) if @unique || !after_load_map.empty? || !limit_map.empty?
2258: 
2259:           records
2260:         end

Private Instance Methods

_load(dependency_map, current, h) click to toggle source

Recursive method that creates associated model objects and associates them to the current model object.

      # File lib/sequel/model/associations.rb, line 2265
2265:         def _load(dependency_map, current, h)
2266:           dependency_map.each do |ta, deps|
2267:             unless key = pk(ta, h)
2268:               ta_h = hfor(ta, h)
2269:               unless ta_h.values.any?
2270:                 assoc_name = alias_map[ta]
2271:                 unless (assoc = current.associations).has_key?(assoc_name)
2272:                   assoc[assoc_name] = type_map[ta] ? [] : nil
2273:                 end
2274:                 next
2275:               end
2276:               key = hkey(ta_h)
2277:             end
2278:             rm, rp, assoc_name, tm, rcm = @ta_map[ta]
2279:             unless rec = rm[key]
2280:               rec = rm[key] = rp.call(hfor(ta, h))
2281:             end
2282: 
2283:             if tm
2284:               unless (assoc = current.associations).has_key?(assoc_name)
2285:                 assoc[assoc_name] = []
2286:               end
2287:               assoc[assoc_name].push(rec) 
2288:               rec.associations[rcm] = current if rcm
2289:             else
2290:               current.associations[assoc_name] ||= rec
2291:             end
2292:             # Recurse into dependencies of the current object
2293:             _load(deps, rec, h) unless deps.empty?
2294:           end
2295:         end
hfor(ta, h) click to toggle source

Return the subhash for the specific table alias ta by parsing the values out of the main hash h

      # File lib/sequel/model/associations.rb, line 2298
2298:         def hfor(ta, h)
2299:           out = {}
2300:           @column_maps[ta].each{|ca, c| out[c] = h[ca]}
2301:           out
2302:         end
hkey(h) click to toggle source

Return a suitable hash key for any subhash h, which is an array of values by column order. This is only used if the primary key cannot be used.

      # File lib/sequel/model/associations.rb, line 2306
2306:         def hkey(h)
2307:           h.sort_by{|x| x[0].to_s}
2308:         end
master_hfor(h) click to toggle source

Return the subhash for the master table by parsing the values out of the main hash h

      # File lib/sequel/model/associations.rb, line 2311
2311:         def master_hfor(h)
2312:           out = {}
2313:           @master_column_map.each{|ca, c| out[c] = h[ca]}
2314:           out
2315:         end
master_pk(h) click to toggle source

Return a primary key value for the master table by parsing it out of the main hash h.

      # File lib/sequel/model/associations.rb, line 2318
2318:         def master_pk(h)
2319:           x = @master_primary_keys
2320:           if x.is_a?(Array)
2321:             unless x == []
2322:               x = x.map{|ca| h[ca]}
2323:               x if x.all?
2324:             end
2325:           else
2326:             h[x]
2327:           end
2328:         end
pk(ta, h) click to toggle source

Return a primary key value for the given table alias by parsing it out of the main hash h.

      # File lib/sequel/model/associations.rb, line 2331
2331:         def pk(ta, h)
2332:           x = primary_keys[ta]
2333:           if x.is_a?(Array)
2334:             unless x == []
2335:               x = x.map{|ca| h[ca]}
2336:               x if x.all?
2337:             end
2338:           else
2339:             h[x]
2340:           end
2341:         end
post_process(records, dependency_map) click to toggle source

If the result set is the result of a cartesian product, then it is possible that there are multiple records for each association when there should only be one. In that case, for each object in all associations loaded via eager_graph, run uniq! on the association to make sure no duplicate records show up. Note that this can cause legitimate duplicate records to be removed.

      # File lib/sequel/model/associations.rb, line 2348
2348:         def post_process(records, dependency_map)
2349:           records.each do |record|
2350:             dependency_map.each do |ta, deps|
2351:               assoc_name = alias_map[ta]
2352:               list = record.send(assoc_name)
2353:               rec_list = if type_map[ta]
2354:                 list.uniq!
2355:                 if lo = limit_map[ta]
2356:                   limit, offset = lo
2357:                   list.replace(list[offset||0, limit])
2358:                 end
2359:                 list
2360:               elsif list
2361:                 [list]
2362:               else
2363:                 []
2364:               end
2365:               record.send(:run_association_callbacks, reflection_map[ta], :after_load, list) if after_load_map[ta]
2366:               post_process(rec_list, deps) if !rec_list.empty? && !deps.empty?
2367:             end
2368:           end
2369:         end

Disabled; run with --debug to generate this.

[Validate]

Generated with the Darkfish Rdoc Generator 1.1.6.