Hash with table alias symbol keys and primary key symbol values (or arrays of primary key symbols for composite key tables)
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.
Hash with table alias symbol keys and subhash values mapping column_alias symbols to the symbol of the real name of the column
Recursive hash with table alias symbol keys mapping to hashes with dependent table alias symbol keys.
Hash with table alias symbol keys and callable values used to create model instances The table alias symbol for the primary model
Hash with table alias symbol keys and primary key symbol values (or arrays of primary key symbols for composite key tables)
Hash with table alias symbol keys and reciprocal association symbol values, used for setting reciprocals for one_to_many associations.
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.
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 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
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
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
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
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
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
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
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
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.
Generated with the Darkfish Rdoc Generator 1.1.6.