lib/sequel/plugins/many_through_many.rb in sequel-3.4.0 vs lib/sequel/plugins/many_through_many.rb in sequel-3.5.0
- old
+ new
@@ -35,10 +35,16 @@
# The table containing the column to use for the associated key when eagerly loading
def associated_key_table
self[:associated_key_table] = self[:final_reverse_edge][:alias]
end
+
+ # The default associated key alias(es) to use when eager loading
+ # associations via eager.
+ def default_associated_key_alias
+ self[:uses_left_composite_keys] ? (0...self[:through].first[:left].length).map{|i| :"x_foreign_key_#{i}_x"} : :x_foreign_key_x
+ end
# The list of joins to use when eager graphing
def edges
self[:edges] || calculate_edges || self[:edges]
end
@@ -101,20 +107,26 @@
# * name - Same as associate, the name of the association.
# * through - The tables and keys to join between the current table and the associated table.
# Must be an array, with elements that are either 3 element arrays, or hashes with keys :table, :left, and :right.
# The required entries in the array/hash are:
# * :table (first array element) - The name of the table to join.
- # * :left (middle array element) - The key joining the table to the previous table
- # * :right (last array element) - The key joining the table to the next table
+ # * :left (middle array element) - The key joining the table to the previous table. Can use an
+ # array of symbols for a composite key association.
+ # * :right (last array element) - The key joining the table to the next table. Can use an
+ # array of symbols for a composite key association.
# If a hash is provided, the following keys are respected when using eager_graph:
# * :block - A proc to use as the block argument to join.
# * :conditions - Extra conditions to add to the JOIN ON clause. Must be a hash or array of two pairs.
# * :join_type - The join type to use for the join, defaults to :left_outer.
# * :only_conditions - Conditions to use for the join instead of the ones specified by the keys.
# * opts - The options for the associaion. Takes the same options as associate, and supports these additional options:
- # * :left_primary_key - column in current table that the first :left option in through points to, as a symbol. Defaults to primary key of current table.
- # * :right_primary_key - column in associated table that the final :right option in through points to, as a symbol. Defaults to primary key of the associated table.
+ # * :left_primary_key - column in current table that the first :left option in
+ # through points to, as a symbol. Defaults to primary key of current table. Can use an
+ # array of symbols for a composite key association.
+ # * :right_primary_key - column in associated table that the final :right option in
+ # through points to, as a symbol. Defaults to primary key of the associated table. Can use an
+ # array of symbols for a composite key association.
# * :uniq - Adds a after_load callback that makes the array of objects unique.
def many_through_many(name, through, opts={}, &block)
associate(:many_through_many, name, opts.merge(:through=>through), &block)
end
@@ -139,28 +151,37 @@
raise(Error, "the through option/argument for many_through_many associations must be an enumerable of arrays or hashes")
end
end
left_key = opts[:left_key] = opts[:through].first[:left]
+ uses_lcks = opts[:uses_left_composite_keys] = left_key.is_a?(Array)
+ left_keys = Array(left_key)
left_pk = (opts[:left_primary_key] ||= self.primary_key)
+ left_pks = Array(left_pk)
opts[:dataset] ||= lambda do
ds = opts.associated_class
- opts.reverse_edges.each{|t| ds = ds.join(t[:table], [[t[:left], t[:right]]], :table_alias=>t[:alias])}
+ opts.reverse_edges.each{|t| ds = ds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias])}
ft = opts[:final_reverse_edge]
- ds.join(ft[:table], [[ft[:left], ft[:right]], [left_key, send(left_pk)]], :table_alias=>ft[:alias])
+ ds.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])) + left_keys.zip(left_pks.map{|k| send(k)}), :table_alias=>ft[:alias])
end
left_key_alias = opts[:left_key_alias] ||= opts.default_associated_key_alias
opts[:eager_loader] ||= lambda do |key_hash, records, associations|
h = key_hash[left_pk]
records.each{|object| object.associations[name] = []}
ds = opts.associated_class
- opts.reverse_edges.each{|t| ds = ds.join(t[:table], [[t[:left], t[:right]]], :table_alias=>t[:alias])}
+ opts.reverse_edges.each{|t| ds = ds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias])}
ft = opts[:final_reverse_edge]
- ds = ds.join(ft[:table], [[ft[:left], ft[:right]], [left_key, h.keys]], :table_alias=>ft[:alias])
+ conds = uses_lcks ? [[left_keys.map{|k| SQL::QualifiedIdentifier.new(ft[:table], k)}, SQL::SQLArray.new(h.keys)]] : [[left_key, h.keys]]
+ ds = ds.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])) + conds, :table_alias=>ft[:alias])
model.eager_loading_dataset(opts, ds, Array(opts.select), associations).all do |assoc_record|
- next unless objects = h[assoc_record.values.delete(left_key_alias)]
+ hash_key = if uses_lcks
+ left_key_alias.map{|k| assoc_record.values.delete(k)}
+ else
+ assoc_record.values.delete(left_key_alias)
+ end
+ next unless objects = h[hash_key]
objects.each{|object| object.associations[name].push(assoc_record)}
end
end
join_type = opts[:graph_join_type]
@@ -170,14 +191,14 @@
use_only_conditions = opts.include?(:graph_only_conditions)
conditions = opts[:graph_conditions]
opts[:eager_grapher] ||= proc do |ds, assoc_alias, table_alias|
iq = table_alias
opts.edges.each do |t|
- ds = ds.graph(t[:table], t.include?(:only_conditions) ? t[:only_conditions] : ([[t[:right], t[:left]]] + t[:conditions]), :select=>false, :table_alias=>ds.send(:eager_unique_table_alias, ds, t[:table]), :join_type=>t[:join_type], :implicit_qualifier=>iq, &t[:block])
+ ds = ds.graph(t[:table], t.include?(:only_conditions) ? t[:only_conditions] : (Array(t[:right]).zip(Array(t[:left])) + t[:conditions]), :select=>false, :table_alias=>ds.send(:eager_unique_table_alias, ds, t[:table]), :join_type=>t[:join_type], :implicit_qualifier=>iq, &t[:block])
iq = nil
end
fe = opts[:final_edge]
- ds.graph(opts.associated_class, use_only_conditions ? only_conditions : ([[opts.right_primary_key, fe[:left]]] + conditions), :select=>select, :table_alias=>assoc_alias, :join_type=>join_type, &graph_block)
+ ds.graph(opts.associated_class, use_only_conditions ? only_conditions : (Array(opts.right_primary_key).zip(Array(fe[:left])) + conditions), :select=>select, :table_alias=>assoc_alias, :join_type=>join_type, &graph_block)
end
def_association_dataset_methods(opts)
end
end