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.



2846
2847
2848
2849
2850
2851
2852
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
2868
2869
2870
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880
2881
2882
2883
2884
2885
2886
2887
2888
2889
2890
2891
2892
2893
2894
2895
2896
2897
2898
2899
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
2934
2935
2936
2937
2938
2939
2940
2941
2942
2943
2944
# File 'lib/sequel/model/associations.rb', line 2846

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]
  limit_map = @limit_map = eager_graph[:limits]
  @unique = eager_graph[:cartesian_product_number] > 1
      
  alias_map = @alias_map = {}
  type_map = @type_map = {}
  after_load_map = @after_load_map = {}
  reflection_map.each do |k, v|
    alias_map[k] = v[:name]
    after_load_map[k] = v[:after_load] unless v[:after_load].empty?
    type_map[k] = if v.returns_array?
      true
    elsif (limit_and_offset = limit_map[k]) && !limit_and_offset.last.nil?
      :offset
    end
  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_map ⇒ Object (readonly)

Hash with table alias symbol keys and after_load hook values



2803
2804
2805
# File 'lib/sequel/model/associations.rb', line 2803

def after_load_map
  @after_load_map
end

#alias_map ⇒ Object (readonly)

Hash with table alias symbol keys and association name values



2806
2807
2808
# File 'lib/sequel/model/associations.rb', line 2806

def alias_map
  @alias_map
end

#column_maps ⇒ Object (readonly)

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



2810
2811
2812
# File 'lib/sequel/model/associations.rb', line 2810

def column_maps
  @column_maps
end

#dependency_map ⇒ Object (readonly)

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



2813
2814
2815
# File 'lib/sequel/model/associations.rb', line 2813

def dependency_map
  @dependency_map
end

#limit_map ⇒ Object (readonly)

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



2816
2817
2818
# File 'lib/sequel/model/associations.rb', line 2816

def limit_map
  @limit_map
end

#master ⇒ Object (readonly)

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



2820
2821
2822
# File 'lib/sequel/model/associations.rb', line 2820

def master
  @master
end

#primary_keys ⇒ Object (readonly)

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



2824
2825
2826
# File 'lib/sequel/model/associations.rb', line 2824

def primary_keys
  @primary_keys
end

#reciprocal_map ⇒ Object (readonly)

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



2828
2829
2830
# File 'lib/sequel/model/associations.rb', line 2828

def reciprocal_map
  @reciprocal_map
end

#records_map ⇒ Object (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.



2832
2833
2834
# File 'lib/sequel/model/associations.rb', line 2832

def records_map
  @records_map
end

#reflection_map ⇒ Object (readonly)

Hash with table alias symbol keys and AssociationReflection values



2835
2836
2837
# File 'lib/sequel/model/associations.rb', line 2835

def reflection_map
  @reflection_map
end

#row_procs ⇒ Object (readonly)

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



2838
2839
2840
# File 'lib/sequel/model/associations.rb', line 2838

def row_procs
  @row_procs
end

#type_map ⇒ Object (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).



2843
2844
2845
# File 'lib/sequel/model/associations.rb', line 2843

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



2948
2949
2950
2951
2952
2953
2954
2955
2956
2957
2958
2959
2960
2961
2962
2963
2964
2965
2966
2967
2968
2969
2970
2971
2972
2973
2974
2975
2976
2977
# File 'lib/sequel/model/associations.rb', line 2948

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