Class: Sequel::Model::Associations::EagerGraphLoader

Inherits:
Object
  • Object
show all
Defined in:
lib/sequel/model/associations.rb

Overview

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.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(dataset) ⇒ EagerGraphLoader

Initialize all of the data structures used during loading.



2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
# File 'lib/sequel/model/associations.rb', line 2285

def initialize(dataset)
  opts = dataset.opts
  eager_graph = opts[:eager_graph]
  @master =  eager_graph[:master]
  requirements = eager_graph[:requirements]
  reflection_map = @reflection_map = eager_graph[:reflections]
  reciprocal_map = @reciprocal_map = eager_graph[:reciprocals]
  @unique = eager_graph[:cartesian_product_number] > 1
      
  alias_map = @alias_map = {}
  type_map = @type_map = {}
  after_load_map = @after_load_map = {}
  limit_map = @limit_map = {}
  reflection_map.each do |k, v|
    alias_map[k] = v[:name]
    type_map[k] = v.returns_array?
    after_load_map[k] = v[:after_load] unless v[:after_load].empty?
    limit_map[k] = v.limit_and_offset if v[:limit]
  end

  # Make dependency map hash out of requirements array for each association.
  # This builds a tree of dependencies that will be used for recursion
  # to ensure that all parts of the object graph are loaded into the
  # appropriate subordinate association.
  @dependency_map = {}
  # Sort the associations by requirements length, so that
  # requirements are added to the dependency hash before their
  # dependencies.
  requirements.sort_by{|a| a[1].length}.each do |ta, deps|
    if deps.empty?
      dependency_map[ta] = {}
    else
      deps = deps.dup
      hash = dependency_map[deps.shift]
      deps.each do |dep|
        hash = hash[dep]
      end
      hash[ta] = {}
    end
  end
      
  # This mapping is used to make sure that duplicate entries in the
  # result set are mapped to a single record.  For example, using a
  # single one_to_many association with 10 associated records,
  # the main object column values appear in the object graph 10 times.
  # We map by primary key, if available, or by the object's entire values,
  # if not. The mapping must be per table, so create sub maps for each table
  # alias.
  records_map = {@master=>{}}
  alias_map.keys.each{|ta| records_map[ta] = {}}
  @records_map = records_map

  datasets = opts[:graph][:table_aliases].to_a.reject{|ta,ds| ds.nil?}
  column_aliases = opts[:graph_aliases] || opts[:graph][:column_aliases]
  primary_keys = {}
  column_maps = {}
  models = {}
  row_procs = {}
  datasets.each do |ta, ds|
    models[ta] = ds.model
    primary_keys[ta] = []
    column_maps[ta] = {}
    row_procs[ta] = ds.row_proc
  end
  column_aliases.each do |col_alias, tc|
    ta, column = tc
    column_maps[ta][col_alias] = column
  end
  column_maps.each do |ta, h|
    pk = models[ta].primary_key
    if pk.is_a?(Array)
      primary_keys[ta] = []
      h.select{|ca, c| primary_keys[ta] << ca if pk.include?(c)}
    else
      h.select{|ca, c| primary_keys[ta] = ca if pk == c}
    end
  end
  @column_maps = column_maps
  @primary_keys = primary_keys
  @row_procs = row_procs

  # For performance, create two special maps for the master table,
  # so you can skip a hash lookup.
  @master_column_map = column_maps[master]
  @master_primary_keys = primary_keys[master]

  # Add a special hash mapping table alias symbols to 5 element arrays that just
  # contain the data in other data structures for that table alias.  This is
  # used for performance, to get all values in one hash lookup instead of
  # separate hash lookups for each data structure.
  ta_map = {}
  alias_map.keys.each do |ta|
    ta_map[ta] = [records_map[ta], row_procs[ta], alias_map[ta], type_map[ta], reciprocal_map[ta]]
  end
  @ta_map = ta_map
end

Instance Attribute Details

#after_load_mapObject (readonly)

Hash with table alias symbol keys and after_load hook values



2242
2243
2244
# File 'lib/sequel/model/associations.rb', line 2242

def after_load_map
  @after_load_map
end

#alias_mapObject (readonly)

Hash with table alias symbol keys and association name values



2245
2246
2247
# File 'lib/sequel/model/associations.rb', line 2245

def alias_map
  @alias_map
end

#column_mapsObject (readonly)

Hash with table alias symbol keys and subhash values mapping column_alias symbols to the symbol of the real name of the column



2249
2250
2251
# File 'lib/sequel/model/associations.rb', line 2249

def column_maps
  @column_maps
end

#dependency_mapObject (readonly)

Recursive hash with table alias symbol keys mapping to hashes with dependent table alias symbol keys.



2252
2253
2254
# File 'lib/sequel/model/associations.rb', line 2252

def dependency_map
  @dependency_map
end

#limit_mapObject (readonly)

Hash with table alias symbol keys and [limit, offset] values



2255
2256
2257
# File 'lib/sequel/model/associations.rb', line 2255

def limit_map
  @limit_map
end

#masterObject (readonly)

Hash with table alias symbol keys and callable values used to create model instances The table alias symbol for the primary model



2259
2260
2261
# File 'lib/sequel/model/associations.rb', line 2259

def master
  @master
end

#primary_keysObject (readonly)

Hash with table alias symbol keys and primary key symbol values (or arrays of primary key symbols for composite key tables)



2263
2264
2265
# File 'lib/sequel/model/associations.rb', line 2263

def primary_keys
  @primary_keys
end

#reciprocal_mapObject (readonly)

Hash with table alias symbol keys and reciprocal association symbol values, used for setting reciprocals for one_to_many associations.



2267
2268
2269
# File 'lib/sequel/model/associations.rb', line 2267

def reciprocal_map
  @reciprocal_map
end

#records_mapObject (readonly)

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.



2271
2272
2273
# File 'lib/sequel/model/associations.rb', line 2271

def records_map
  @records_map
end

#reflection_mapObject (readonly)

Hash with table alias symbol keys and AssociationReflection values



2274
2275
2276
# File 'lib/sequel/model/associations.rb', line 2274

def reflection_map
  @reflection_map
end

#row_procsObject (readonly)

Hash with table alias symbol keys and callable values used to create model instances



2277
2278
2279
# File 'lib/sequel/model/associations.rb', line 2277

def row_procs
  @row_procs
end

#type_mapObject (readonly)

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).



2282
2283
2284
# File 'lib/sequel/model/associations.rb', line 2282

def type_map
  @type_map
end

Instance Method Details

#load(hashes) ⇒ Object

Return an array of primary model instances with the associations cache prepopulated for all model objects (both primary and associated).



2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
# File 'lib/sequel/model/associations.rb', line 2384

def load(hashes)
  master = master()
      
  # Assign to local variables for speed increase
  rp = row_procs[master]
  rm = records_map[master]
  dm = dependency_map

  # This will hold the final record set that we will be replacing the object graph with.
  records = []

  hashes.each do |h|
    unless key = master_pk(h)
      key = hkey(master_hfor(h))
    end
    unless primary_record = rm[key]
      primary_record = rm[key] = rp.call(master_hfor(h))
      # Only add it to the list of records to return if it is a new record
      records.push(primary_record)
    end
    # Build all associations for the current object and it's dependencies
    _load(dm, primary_record, h)
  end
      
  # Remove duplicate records from all associations if this graph could possibly be a cartesian product
  # Run after_load procs if there are any
  post_process(records, dm) if @unique || !after_load_map.empty? || !limit_map.empty?

  records
end