| 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 2023
2023: def initialize(dataset)
2024: opts = dataset.opts
2025: eager_graph = opts[:eager_graph]
2026: @master = eager_graph[:master]
2027: requirements = eager_graph[:requirements]
2028: reflection_map = @reflection_map = eager_graph[:reflections]
2029: reciprocal_map = @reciprocal_map = eager_graph[:reciprocals]
2030: @unique = eager_graph[:cartesian_product_number] > 1
2031:
2032: alias_map = @alias_map = {}
2033: type_map = @type_map = {}
2034: after_load_map = @after_load_map = {}
2035: limit_map = @limit_map = {}
2036: reflection_map.each do |k, v|
2037: alias_map[k] = v[:name]
2038: type_map[k] = v.returns_array?
2039: after_load_map[k] = v[:after_load] unless v[:after_load].empty?
2040: limit_map[k] = v.limit_and_offset if v[:limit]
2041: end
2042:
2043: # Make dependency map hash out of requirements array for each association.
2044: # This builds a tree of dependencies that will be used for recursion
2045: # to ensure that all parts of the object graph are loaded into the
2046: # appropriate subordinate association.
2047: @dependency_map = {}
2048: # Sort the associations by requirements length, so that
2049: # requirements are added to the dependency hash before their
2050: # dependencies.
2051: requirements.sort_by{|a| a[1].length}.each do |ta, deps|
2052: if deps.empty?
2053: dependency_map[ta] = {}
2054: else
2055: deps = deps.dup
2056: hash = dependency_map[deps.shift]
2057: deps.each do |dep|
2058: hash = hash[dep]
2059: end
2060: hash[ta] = {}
2061: end
2062: end
2063:
2064: # This mapping is used to make sure that duplicate entries in the
2065: # result set are mapped to a single record. For example, using a
2066: # single one_to_many association with 10 associated records,
2067: # the main object column values appear in the object graph 10 times.
2068: # We map by primary key, if available, or by the object's entire values,
2069: # if not. The mapping must be per table, so create sub maps for each table
2070: # alias.
2071: records_map = {@master=>{}}
2072: alias_map.keys.each{|ta| records_map[ta] = {}}
2073: @records_map = records_map
2074:
2075: datasets = opts[:graph][:table_aliases].to_a.reject{|ta,ds| ds.nil?}
2076: column_aliases = opts[:graph_aliases] || opts[:graph][:column_aliases]
2077: primary_keys = {}
2078: column_maps = {}
2079: models = {}
2080: row_procs = {}
2081: datasets.each do |ta, ds|
2082: models[ta] = ds.model
2083: primary_keys[ta] = []
2084: column_maps[ta] = {}
2085: row_procs[ta] = ds.row_proc
2086: end
2087: column_aliases.each do |col_alias, tc|
2088: ta, column = tc
2089: column_maps[ta][col_alias] = column
2090: end
2091: column_maps.each do |ta, h|
2092: pk = models[ta].primary_key
2093: if pk.is_a?(Array)
2094: primary_keys[ta] = []
2095: h.select{|ca, c| primary_keys[ta] << ca if pk.include?(c)}
2096: else
2097: h.select{|ca, c| primary_keys[ta] = ca if pk == c}
2098: end
2099: end
2100: @column_maps = column_maps
2101: @primary_keys = primary_keys
2102: @row_procs = row_procs
2103:
2104: # For performance, create two special maps for the master table,
2105: # so you can skip a hash lookup.
2106: @master_column_map = column_maps[master]
2107: @master_primary_keys = primary_keys[master]
2108:
2109: # Add a special hash mapping table alias symbols to 5 element arrays that just
2110: # contain the data in other data structures for that table alias. This is
2111: # used for performance, to get all values in one hash lookup instead of
2112: # separate hash lookups for each data structure.
2113: ta_map = {}
2114: alias_map.keys.each do |ta|
2115: ta_map[ta] = [records_map[ta], row_procs[ta], alias_map[ta], type_map[ta], reciprocal_map[ta]]
2116: end
2117: @ta_map = ta_map
2118: 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 2122
2122: def load(hashes)
2123: master = master()
2124:
2125: # Assign to local variables for speed increase
2126: rp = row_procs[master]
2127: rm = records_map[master]
2128: dm = dependency_map
2129:
2130: # This will hold the final record set that we will be replacing the object graph with.
2131: records = []
2132:
2133: hashes.each do |h|
2134: unless key = master_pk(h)
2135: key = hkey(master_hfor(h))
2136: end
2137: unless primary_record = rm[key]
2138: primary_record = rm[key] = rp.call(master_hfor(h))
2139: # Only add it to the list of records to return if it is a new record
2140: records.push(primary_record)
2141: end
2142: # Build all associations for the current object and it's dependencies
2143: _load(dm, primary_record, h)
2144: end
2145:
2146: # Remove duplicate records from all associations if this graph could possibly be a cartesian product
2147: # Run after_load procs if there are any
2148: post_process(records, dm) if @unique || !after_load_map.empty? || !limit_map.empty?
2149:
2150: records
2151: end