mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Rename Reflection#through_reflection_chain and #through_options to Reflection#chain and Reflection#options as they now no longer relate solely to through associations.
This commit is contained in:
parent
6490d65234
commit
2d3d9e3531
5 changed files with 57 additions and 54 deletions
|
@ -4,7 +4,7 @@ module ActiveRecord
|
||||||
attr_reader :association, :alias_tracker
|
attr_reader :association, :alias_tracker
|
||||||
|
|
||||||
delegate :klass, :owner, :reflection, :interpolate, :to => :association
|
delegate :klass, :owner, :reflection, :interpolate, :to => :association
|
||||||
delegate :through_reflection_chain, :through_conditions, :options, :source_options, :to => :reflection
|
delegate :chain, :conditions, :options, :source_options, :to => :reflection
|
||||||
|
|
||||||
def initialize(association)
|
def initialize(association)
|
||||||
@association = association
|
@association = association
|
||||||
|
@ -50,7 +50,7 @@ module ActiveRecord
|
||||||
def add_constraints(scope)
|
def add_constraints(scope)
|
||||||
tables = construct_tables
|
tables = construct_tables
|
||||||
|
|
||||||
through_reflection_chain.each_with_index do |reflection, i|
|
chain.each_with_index do |reflection, i|
|
||||||
table, foreign_table = tables.shift, tables.first
|
table, foreign_table = tables.shift, tables.first
|
||||||
|
|
||||||
if reflection.source_macro == :has_and_belongs_to_many
|
if reflection.source_macro == :has_and_belongs_to_many
|
||||||
|
@ -73,10 +73,10 @@ module ActiveRecord
|
||||||
foreign_key = reflection.active_record_primary_key
|
foreign_key = reflection.active_record_primary_key
|
||||||
end
|
end
|
||||||
|
|
||||||
if reflection == through_reflection_chain.last
|
if reflection == chain.last
|
||||||
scope = scope.where(table[key].eq(owner[foreign_key]))
|
scope = scope.where(table[key].eq(owner[foreign_key]))
|
||||||
|
|
||||||
through_conditions[i].each do |condition|
|
conditions[i].each do |condition|
|
||||||
if options[:through] && condition.is_a?(Hash)
|
if options[:through] && condition.is_a?(Hash)
|
||||||
condition = { table.name => condition }
|
condition = { table.name => condition }
|
||||||
end
|
end
|
||||||
|
@ -86,7 +86,7 @@ module ActiveRecord
|
||||||
else
|
else
|
||||||
constraint = table[key].eq foreign_table[foreign_key]
|
constraint = table[key].eq foreign_table[foreign_key]
|
||||||
|
|
||||||
join = inner_join(foreign_table, reflection, constraint, *through_conditions[i])
|
join = inner_join(foreign_table, reflection, constraint, *conditions[i])
|
||||||
scope = scope.joins(join)
|
scope = scope.joins(join)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -96,7 +96,7 @@ module ActiveRecord
|
||||||
|
|
||||||
def construct_tables
|
def construct_tables
|
||||||
tables = []
|
tables = []
|
||||||
through_reflection_chain.each do |reflection|
|
chain.each do |reflection|
|
||||||
tables << alias_tracker.aliased_table_for(
|
tables << alias_tracker.aliased_table_for(
|
||||||
table_name_for(reflection),
|
table_name_for(reflection),
|
||||||
table_alias_for(reflection, reflection != self.reflection)
|
table_alias_for(reflection, reflection != self.reflection)
|
||||||
|
|
|
@ -22,7 +22,7 @@ module ActiveRecord
|
||||||
|
|
||||||
attr_reader :tables
|
attr_reader :tables
|
||||||
|
|
||||||
delegate :options, :through_reflection, :source_reflection, :through_reflection_chain, :to => :reflection
|
delegate :options, :through_reflection, :source_reflection, :chain, :to => :reflection
|
||||||
delegate :table, :table_name, :to => :parent, :prefix => :parent
|
delegate :table, :table_name, :to => :parent, :prefix => :parent
|
||||||
delegate :alias_tracker, :to => :join_dependency
|
delegate :alias_tracker, :to => :join_dependency
|
||||||
|
|
||||||
|
@ -57,14 +57,12 @@ module ActiveRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def join_to(relation)
|
def join_to(relation)
|
||||||
# The chain starts with the target table, but we want to end with it here (makes
|
|
||||||
# more sense in this context)
|
|
||||||
chain = through_reflection_chain.reverse
|
|
||||||
|
|
||||||
foreign_table = parent_table
|
foreign_table = parent_table
|
||||||
index = 0
|
index = 0
|
||||||
|
|
||||||
chain.each do |reflection|
|
# The chain starts with the target table, but we want to end with it here (makes
|
||||||
|
# more sense in this context), so we reverse
|
||||||
|
chain.reverse.each do |reflection|
|
||||||
table = tables[index]
|
table = tables[index]
|
||||||
conditions = []
|
conditions = []
|
||||||
|
|
||||||
|
@ -178,7 +176,7 @@ module ActiveRecord
|
||||||
# later generate joins for. We must do this in advance in order to correctly allocate
|
# later generate joins for. We must do this in advance in order to correctly allocate
|
||||||
# the proper alias.
|
# the proper alias.
|
||||||
def setup_tables
|
def setup_tables
|
||||||
@tables = through_reflection_chain.map do |reflection|
|
@tables = chain.map do |reflection|
|
||||||
table = alias_tracker.aliased_table_for(
|
table = alias_tracker.aliased_table_for(
|
||||||
reflection.table_name,
|
reflection.table_name,
|
||||||
table_alias_for(reflection, reflection != self.reflection)
|
table_alias_for(reflection, reflection != self.reflection)
|
||||||
|
@ -200,7 +198,7 @@ module ActiveRecord
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# The joins are generated from the through_reflection_chain in reverse order, so
|
# The joins are generated from the chain in reverse order, so
|
||||||
# reverse the tables too (but it's important to generate the aliases in the 'forward'
|
# reverse the tables too (but it's important to generate the aliases in the 'forward'
|
||||||
# order, which is why we only do the reversal now.
|
# order, which is why we only do the reversal now.
|
||||||
@tables.reverse!
|
@tables.reverse!
|
||||||
|
@ -219,7 +217,7 @@ module ActiveRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def reflection_conditions(index, table)
|
def reflection_conditions(index, table)
|
||||||
reflection.through_conditions.reverse[index].map do |condition|
|
reflection.conditions.reverse[index].map do |condition|
|
||||||
process_conditions(condition, table.table_alias || table.name)
|
process_conditions(condition, table.table_alias || table.name)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,8 +3,7 @@ module ActiveRecord
|
||||||
module Associations
|
module Associations
|
||||||
module ThroughAssociation #:nodoc:
|
module ThroughAssociation #:nodoc:
|
||||||
|
|
||||||
delegate :source_options, :through_options, :source_reflection, :through_reflection,
|
delegate :source_reflection, :through_reflection, :chain, :to => :reflection
|
||||||
:through_reflection_chain, :through_conditions, :to => :reflection
|
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
|
@ -18,7 +17,7 @@ module ActiveRecord
|
||||||
# itself.
|
# itself.
|
||||||
def target_scope
|
def target_scope
|
||||||
scope = super
|
scope = super
|
||||||
through_reflection_chain[1..-1].each do |reflection|
|
chain[1..-1].each do |reflection|
|
||||||
scope = scope.merge(reflection.klass.scoped)
|
scope = scope.merge(reflection.klass.scoped)
|
||||||
end
|
end
|
||||||
scope
|
scope
|
||||||
|
|
|
@ -262,23 +262,28 @@ module ActiveRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def through_reflection
|
def through_reflection
|
||||||
false
|
nil
|
||||||
end
|
|
||||||
|
|
||||||
def through_reflection_chain
|
|
||||||
[self]
|
|
||||||
end
|
|
||||||
|
|
||||||
def through_conditions
|
|
||||||
conditions = [options[:conditions]].compact
|
|
||||||
conditions << { type => active_record.base_class.name } if options[:as]
|
|
||||||
[conditions]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def source_reflection
|
def source_reflection
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# A chain of reflections from this one back to the owner. For more see the explanation in
|
||||||
|
# ThroughReflection.
|
||||||
|
def chain
|
||||||
|
[self]
|
||||||
|
end
|
||||||
|
|
||||||
|
# An array of arrays of conditions. Each item in the outside array corresponds to a reflection
|
||||||
|
# in the #chain. The inside arrays are simply conditions (and each condition may itself be
|
||||||
|
# a hash, array, arel predicate, etc...)
|
||||||
|
def conditions
|
||||||
|
conditions = [options[:conditions]].compact
|
||||||
|
conditions << { type => active_record.base_class.name } if options[:as]
|
||||||
|
[conditions]
|
||||||
|
end
|
||||||
|
|
||||||
alias :source_macro :macro
|
alias :source_macro :macro
|
||||||
|
|
||||||
def has_inverse?
|
def has_inverse?
|
||||||
|
@ -401,33 +406,34 @@ module ActiveRecord
|
||||||
@through_reflection ||= active_record.reflect_on_association(options[:through])
|
@through_reflection ||= active_record.reflect_on_association(options[:through])
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns an array of AssociationReflection objects which are involved in this through
|
# Returns an array of reflections which are involved in this association. Each item in the
|
||||||
# association. Each item in the array corresponds to a table which will be part of the
|
# array corresponds to a table which will be part of the query for this association.
|
||||||
# query for this association.
|
|
||||||
#
|
#
|
||||||
# If the source reflection is itself a ThroughReflection, then we don't include self in
|
# If the source reflection is itself a ThroughReflection, then we don't include self in
|
||||||
# the chain, but just defer to the source reflection.
|
# the chain, but just defer to the source reflection.
|
||||||
#
|
#
|
||||||
# The chain is built by recursively calling through_reflection_chain on the source
|
# The chain is built by recursively calling #chain on the source reflection and the through
|
||||||
# reflection and the through reflection. The base case for the recursion is a normal
|
# reflection. The base case for the recursion is a normal association, which just returns
|
||||||
# association, which just returns [self] for its through_reflection_chain.
|
# [self] as its #chain.
|
||||||
def through_reflection_chain
|
def chain
|
||||||
@through_reflection_chain ||= begin
|
@chain ||= begin
|
||||||
if source_reflection.source_reflection
|
if source_reflection.source_reflection
|
||||||
# If the source reflection has its own source reflection, then the chain must start
|
# If the source reflection has its own source reflection, then the chain must start
|
||||||
# by getting us to that source reflection.
|
# by getting us to that source reflection.
|
||||||
chain = source_reflection.through_reflection_chain
|
chain = source_reflection.chain
|
||||||
else
|
else
|
||||||
# If the source reflection does not go through another reflection, then we can get
|
# If the source reflection does not go through another reflection, then we can get
|
||||||
# to this reflection directly, and so start the chain here
|
# to this reflection directly, and so start the chain here
|
||||||
#
|
#
|
||||||
# It is important to use self, rather than the source_reflection, because self
|
# It is important to use self, rather than the source_reflection, because self
|
||||||
# may has a :source_type option which needs to be used.
|
# may has a :source_type option which needs to be used.
|
||||||
|
#
|
||||||
|
# FIXME: Not sure this is correct justification now that we have #conditions
|
||||||
chain = [self]
|
chain = [self]
|
||||||
end
|
end
|
||||||
|
|
||||||
# Recursively build the rest of the chain
|
# Recursively build the rest of the chain
|
||||||
chain += through_reflection.through_reflection_chain
|
chain += through_reflection.chain
|
||||||
|
|
||||||
# Finally return the completed chain
|
# Finally return the completed chain
|
||||||
chain
|
chain
|
||||||
|
@ -451,18 +457,18 @@ module ActiveRecord
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
# There may be conditions on Person.comment_tags, Article.comment_tags and/or Comment.tags,
|
# There may be conditions on Person.comment_tags, Article.comment_tags and/or Comment.tags,
|
||||||
# but only Comment.tags will be represented in the through_reflection_chain. So this method
|
# but only Comment.tags will be represented in the #chain. So this method creates an array
|
||||||
# creates an array of conditions corresponding to the through_reflection_chain. Each item in
|
# of conditions corresponding to the chain. Each item in the #conditions array corresponds
|
||||||
# the through_conditions array corresponds to an item in the through_reflection_chain, and is
|
# to an item in the #chain, and is itself an array of conditions from an arbitrary number
|
||||||
# itself an array of conditions from an arbitrary number of relevant reflections.
|
# of relevant reflections, plus any :source_type or polymorphic :as constraints.
|
||||||
def through_conditions
|
def conditions
|
||||||
@through_conditions ||= begin
|
@conditions ||= begin
|
||||||
conditions = source_reflection.through_conditions
|
conditions = source_reflection.conditions
|
||||||
|
|
||||||
# Add to it the conditions from this reflection if necessary.
|
# Add to it the conditions from this reflection if necessary.
|
||||||
conditions.first << options[:conditions] if options[:conditions]
|
conditions.first << options[:conditions] if options[:conditions]
|
||||||
|
|
||||||
through_conditions = through_reflection.through_conditions
|
through_conditions = through_reflection.conditions
|
||||||
|
|
||||||
if options[:source_type]
|
if options[:source_type]
|
||||||
through_conditions.first << { foreign_type => options[:source_type] }
|
through_conditions.first << { foreign_type => options[:source_type] }
|
||||||
|
@ -476,14 +482,14 @@ module ActiveRecord
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# The macro used by the source association
|
||||||
def source_macro
|
def source_macro
|
||||||
source_reflection.source_macro
|
source_reflection.source_macro
|
||||||
end
|
end
|
||||||
|
|
||||||
# A through association is nested iff there would be more than one join table
|
# A through association is nested iff there would be more than one join table
|
||||||
def nested?
|
def nested?
|
||||||
through_reflection_chain.length > 2 ||
|
chain.length > 2 || through_reflection.macro == :has_and_belongs_to_many
|
||||||
through_reflection.macro == :has_and_belongs_to_many
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# We want to use the klass from this reflection, rather than just delegate straight to
|
# We want to use the klass from this reflection, rather than just delegate straight to
|
||||||
|
|
|
@ -202,24 +202,24 @@ class ReflectionTest < ActiveRecord::TestCase
|
||||||
assert_kind_of ThroughReflection, Subscriber.reflect_on_association(:books)
|
assert_kind_of ThroughReflection, Subscriber.reflect_on_association(:books)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_through_reflection_chain
|
def test_chain
|
||||||
expected = [
|
expected = [
|
||||||
Author.reflect_on_association(:essay_categories),
|
Author.reflect_on_association(:essay_categories),
|
||||||
Author.reflect_on_association(:essays),
|
Author.reflect_on_association(:essays),
|
||||||
Organization.reflect_on_association(:authors)
|
Organization.reflect_on_association(:authors)
|
||||||
]
|
]
|
||||||
actual = Organization.reflect_on_association(:author_essay_categories).through_reflection_chain
|
actual = Organization.reflect_on_association(:author_essay_categories).chain
|
||||||
|
|
||||||
assert_equal expected, actual
|
assert_equal expected, actual
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_through_conditions
|
def test_conditions
|
||||||
expected = [
|
expected = [
|
||||||
["tags.name = 'Blue'"],
|
["tags.name = 'Blue'"],
|
||||||
["taggings.comment = 'first'", {"taggable_type"=>"Post"}],
|
["taggings.comment = 'first'", {"taggable_type"=>"Post"}],
|
||||||
["posts.title LIKE 'misc post%'"]
|
["posts.title LIKE 'misc post%'"]
|
||||||
]
|
]
|
||||||
actual = Author.reflect_on_association(:misc_post_first_blue_tags).through_conditions
|
actual = Author.reflect_on_association(:misc_post_first_blue_tags).conditions
|
||||||
assert_equal expected, actual
|
assert_equal expected, actual
|
||||||
|
|
||||||
expected = [
|
expected = [
|
||||||
|
@ -227,7 +227,7 @@ class ReflectionTest < ActiveRecord::TestCase
|
||||||
[{"taggable_type"=>"Post"}],
|
[{"taggable_type"=>"Post"}],
|
||||||
[]
|
[]
|
||||||
]
|
]
|
||||||
actual = Author.reflect_on_association(:misc_post_first_blue_tags_2).through_conditions
|
actual = Author.reflect_on_association(:misc_post_first_blue_tags_2).conditions
|
||||||
assert_equal expected, actual
|
assert_equal expected, actual
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue