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.

[View source]

3785
3786
3787
3788
3789
3790
3791
3792
3793
3794
3795
3796
3797
3798
3799
3800
3801
3802
3803
3804
3805
3806
3807
3808
3809
3810
3811
3812
3813
3814
3815
3816
3817
3818
3819
3820
3821
3822
3823
3824
3825
3826
3827
3828
3829
3830
3831
3832
3833
3834
3835
3836
3837
3838
3839
3840
3841
3842
3843
3844
3845
3846
3847
3848
3849
3850
3851
3852
3853
3854
3855
3856
3857
3858
3859
3860
3861
3862
3863
3864
3865
3866
3867
3868
3869
3870
3871
3872
3873
3874
3875
3876
3877
3878
3879
3880
3881
# File 'lib/sequel/model/associations.rb', line 3785

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] if v[:after_load]
    type_map[k] = if v.returns_array?
      true
    elsif (limit_and_offset = limit_map[k]) && !limit_and_offset.last.nil?
      :offset
    end
  end
  after_load_map.freeze
  alias_map.freeze
  type_map.freeze

  # 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 = @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
  freezer = lambda do |h|
    h.freeze
    h.each_value(&freezer)
  end
  freezer.call(dependency_map)
      
  datasets = opts[:graph][:table_aliases].to_a.reject{|ta,ds| ds.nil?}
  column_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.freeze
  @primary_keys = primary_keys.freeze
  @row_procs = row_procs.freeze

  # 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.each_key do |ta|
    ta_map[ta] = [row_procs[ta], alias_map[ta], type_map[ta], reciprocal_map[ta]].freeze
  end
  @ta_map = ta_map.freeze
  freeze
end

Instance Attribute Details

#after_load_mapObject (readonly)

Hash with table alias symbol keys and after_load hook values


3743
3744
3745
# File 'lib/sequel/model/associations.rb', line 3743

def after_load_map
  @after_load_map
end

#alias_mapObject (readonly)

Hash with table alias symbol keys and association name values


3746
3747
3748
# File 'lib/sequel/model/associations.rb', line 3746

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


3750
3751
3752
# File 'lib/sequel/model/associations.rb', line 3750

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.


3753
3754
3755
# File 'lib/sequel/model/associations.rb', line 3753

def dependency_map
  @dependency_map
end

#limit_mapObject (readonly)

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


3756
3757
3758
# File 'lib/sequel/model/associations.rb', line 3756

def limit_map
  @limit_map
end

#masterObject (readonly)

The table alias symbol for the primary model


3759
3760
3761
# File 'lib/sequel/model/associations.rb', line 3759

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)


3763
3764
3765
# File 'lib/sequel/model/associations.rb', line 3763

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.


3767
3768
3769
# File 'lib/sequel/model/associations.rb', line 3767

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.


3771
3772
3773
# File 'lib/sequel/model/associations.rb', line 3771

def records_map
  @records_map
end

#reflection_mapObject (readonly)

Hash with table alias symbol keys and AssociationReflection values


3774
3775
3776
# File 'lib/sequel/model/associations.rb', line 3774

def reflection_map
  @reflection_map
end

#row_procsObject (readonly)

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


3777
3778
3779
# File 'lib/sequel/model/associations.rb', line 3777

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


3782
3783
3784
# File 'lib/sequel/model/associations.rb', line 3782

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

[View source]

3885
3886
3887
3888
3889
3890
3891
3892
3893
3894
3895
3896
3897
3898
3899
3900
3901
3902
3903
3904
3905
3906
3907
3908
3909
3910
3911
3912
3913
3914
3915
3916
3917
3918
3919
3920
3921
3922
3923
3924
3925
3926
3927
3928
3929
# File 'lib/sequel/model/associations.rb', line 3885

def load(hashes)
  # 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 = records_map = {}
  alias_map.keys.each{|ta| records_map[ta] = {}}

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

  records_map.freeze

  # 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_map.each_value(&:freeze)
  freeze

  records
end