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.



3968
3969
3970
3971
3972
3973
3974
3975
3976
3977
3978
3979
3980
3981
3982
3983
3984
3985
3986
3987
3988
3989
3990
3991
3992
3993
3994
3995
3996
3997
3998
3999
4000
4001
4002
4003
4004
4005
4006
4007
4008
4009
4010
4011
4012
4013
4014
4015
4016
4017
4018
4019
4020
4021
4022
4023
4024
4025
4026
4027
4028
4029
4030
4031
4032
4033
4034
4035
4036
4037
4038
4039
4040
4041
4042
4043
4044
4045
4046
4047
4048
4049
4050
4051
4052
4053
4054
4055
4056
4057
4058
4059
4060
4061
4062
4063
4064
# File 'lib/sequel/model/associations.rb', line 3968

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



3926
3927
3928
# File 'lib/sequel/model/associations.rb', line 3926

def after_load_map
  @after_load_map
end

#alias_mapObject (readonly)

Hash with table alias symbol keys and association name values



3929
3930
3931
# File 'lib/sequel/model/associations.rb', line 3929

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



3933
3934
3935
# File 'lib/sequel/model/associations.rb', line 3933

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.



3936
3937
3938
# File 'lib/sequel/model/associations.rb', line 3936

def dependency_map
  @dependency_map
end

#limit_mapObject (readonly)

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



3939
3940
3941
# File 'lib/sequel/model/associations.rb', line 3939

def limit_map
  @limit_map
end

#masterObject (readonly)

The table alias symbol for the primary model



3942
3943
3944
# File 'lib/sequel/model/associations.rb', line 3942

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)



3946
3947
3948
# File 'lib/sequel/model/associations.rb', line 3946

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.



3950
3951
3952
# File 'lib/sequel/model/associations.rb', line 3950

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.



3954
3955
3956
# File 'lib/sequel/model/associations.rb', line 3954

def records_map
  @records_map
end

#reflection_mapObject (readonly)

Hash with table alias symbol keys and AssociationReflection values



3957
3958
3959
# File 'lib/sequel/model/associations.rb', line 3957

def reflection_map
  @reflection_map
end

#row_procsObject (readonly)

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



3960
3961
3962
# File 'lib/sequel/model/associations.rb', line 3960

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



3965
3966
3967
# File 'lib/sequel/model/associations.rb', line 3965

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



4068
4069
4070
4071
4072
4073
4074
4075
4076
4077
4078
4079
4080
4081
4082
4083
4084
4085
4086
4087
4088
4089
4090
4091
4092
4093
4094
4095
4096
4097
4098
4099
4100
4101
4102
4103
4104
4105
4106
4107
4108
4109
4110
4111
4112
# File 'lib/sequel/model/associations.rb', line 4068

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