1
0
Fork 0
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:
Jon Leighton 2011-03-10 19:28:26 +00:00
parent 6490d65234
commit 2d3d9e3531
5 changed files with 57 additions and 54 deletions

View file

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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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