Skip to content

Commit 3054971

Browse files
committed
apply [6408] to stable
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/1-2-stable@6410 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
1 parent 736cca8 commit 3054971

File tree

6 files changed

+94
-47
lines changed

6 files changed

+94
-47
lines changed

activerecord/CHANGELOG

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
*1.15.3* (March 12th, 2007)
22

3+
* Allow a polymorphic :source for has_many :through associations. Closes #7143 [protocool]
4+
35
* Consistently quote primary key column names. #7763 [toolmantim]
46

57
* Fixtures: fix YAML ordered map support. #2665 [Manuel Holtgrewe, nfbuckley]

activerecord/lib/active_record/associations.rb

Lines changed: 61 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,13 @@ def initialize(owner_class_name, reflection, source_reflection)
2020
super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' on the polymorphic object '#{source_reflection.class_name}##{source_reflection.name}'.")
2121
end
2222
end
23-
23+
24+
class HasManyThroughAssociationPointlessSourceTypeError < ActiveRecordError #:nodoc:
25+
def initialize(owner_class_name, reflection, source_reflection)
26+
super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' with a :source_type option if the '#{reflection.through_reflection.class_name}##{source_reflection.name}' is not polymorphic. Try removing :source_type on your association.")
27+
end
28+
end
29+
2430
class HasManyThroughSourceAssociationNotFoundError < ActiveRecordError #:nodoc:
2531
def initialize(reflection)
2632
through_reflection = reflection.through_reflection
@@ -529,6 +535,8 @@ module ClassMethods
529535
# * <tt>:source</tt>: Specifies the source association name used by <tt>has_many :through</tt> queries. Only use it if the name cannot be
530536
# inferred from the association. <tt>has_many :subscribers, :through => :subscriptions</tt> will look for either +:subscribers+ or
531537
# +:subscriber+ on +Subscription+, unless a +:source+ is given.
538+
# * <tt>:source_type</tt>: Specifies type of the source association used by <tt>has_many :through</tt> queries where the source association
539+
# is a polymorphic belongs_to.
532540
# * <tt>:uniq</tt> - if set to true, duplicates will be omitted from the collection. Useful in conjunction with :through.
533541
#
534542
# Option examples:
@@ -1087,7 +1095,7 @@ def create_has_many_reflection(association_id, options, &extension)
10871095
:class_name, :table_name, :foreign_key,
10881096
:exclusively_dependent, :dependent,
10891097
:select, :conditions, :include, :order, :group, :limit, :offset,
1090-
:as, :through, :source,
1098+
:as, :through, :source, :source_type,
10911099
:uniq,
10921100
:finder_sql, :counter_sql,
10931101
:before_add, :after_add, :before_remove, :after_remove,
@@ -1491,57 +1499,65 @@ def association_join
14911499
case
14921500
when reflection.macro == :has_many && reflection.options[:through]
14931501
through_conditions = through_reflection.options[:conditions] ? "AND #{interpolate_sql(sanitize_sql(through_reflection.options[:conditions]))}" : ''
1502+
1503+
jt_foreign_key = jt_as_extra = jt_source_extra = jt_sti_extra = nil
1504+
first_key = second_key = as_extra = nil
1505+
14941506
if through_reflection.options[:as] # has_many :through against a polymorphic join
1495-
polymorphic_foreign_key = through_reflection.options[:as].to_s + '_id'
1496-
polymorphic_foreign_type = through_reflection.options[:as].to_s + '_type'
1497-
1498-
" LEFT OUTER JOIN %s ON (%s.%s = %s.%s AND %s.%s = %s) " % [
1499-
table_alias_for(through_reflection.klass.table_name, aliased_join_table_name),
1500-
aliased_join_table_name, polymorphic_foreign_key,
1501-
parent.aliased_table_name, parent.primary_key,
1502-
aliased_join_table_name, polymorphic_foreign_type, klass.quote_value(parent.active_record.base_class.name)] +
1503-
" LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [table_name_and_alias,
1504-
aliased_table_name, primary_key, aliased_join_table_name, options[:foreign_key] || reflection.klass.to_s.classify.foreign_key
1507+
jt_foreign_key = through_reflection.options[:as].to_s + '_id'
1508+
jt_as_extra = " AND %s.%s = %s" % [
1509+
aliased_join_table_name, reflection.active_record.connection.quote_column_name(through_reflection.options[:as].to_s + '_type'),
1510+
klass.quote_value(parent.active_record.base_class.name)
15051511
]
15061512
else
1507-
if source_reflection.macro == :has_many && source_reflection.options[:as]
1508-
" LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [
1509-
table_alias_for(through_reflection.klass.table_name, aliased_join_table_name), aliased_join_table_name,
1510-
through_reflection.primary_key_name,
1511-
parent.aliased_table_name, parent.primary_key] +
1512-
" LEFT OUTER JOIN %s ON %s.%s = %s.%s AND %s.%s = %s " % [
1513-
table_name_and_alias,
1514-
aliased_table_name, "#{source_reflection.options[:as]}_id",
1515-
aliased_join_table_name, options[:foreign_key] || primary_key,
1516-
aliased_table_name, "#{source_reflection.options[:as]}_type",
1513+
jt_foreign_key = through_reflection.primary_key_name
1514+
end
1515+
1516+
case source_reflection.macro
1517+
when :has_many
1518+
if source_reflection.options[:as]
1519+
first_key = "#{source_reflection.options[:as]}_id"
1520+
second_key = options[:foreign_key] || primary_key
1521+
as_extra = " AND %s.%s = %s" % [
1522+
aliased_table_name, reflection.active_record.connection.quote_column_name("#{source_reflection.options[:as]}_type"),
15171523
klass.quote_value(source_reflection.active_record.base_class.name)
15181524
]
15191525
else
1520-
case source_reflection.macro
1521-
when :belongs_to
1522-
first_key = primary_key
1523-
second_key = source_reflection.options[:foreign_key] || klass.to_s.classify.foreign_key
1524-
extra = nil
1525-
when :has_many
1526-
first_key = through_reflection.klass.base_class.to_s.classify.foreign_key
1527-
second_key = options[:foreign_key] || primary_key
1528-
extra = through_reflection.klass.descends_from_active_record? ? nil :
1529-
" AND %s.%s = %s" % [
1530-
aliased_join_table_name,
1531-
reflection.active_record.connection.quote_column_name(through_reflection.active_record.inheritance_column),
1532-
through_reflection.klass.quote_value(through_reflection.klass.name.demodulize)]
1533-
end
1534-
" LEFT OUTER JOIN %s ON (%s.%s = %s.%s%s) " % [
1535-
table_alias_for(through_reflection.klass.table_name, aliased_join_table_name),
1536-
aliased_join_table_name, through_reflection.primary_key_name,
1537-
parent.aliased_table_name, parent.primary_key, extra] +
1538-
" LEFT OUTER JOIN %s ON (%s.%s = %s.%s) " % [
1539-
table_name_and_alias,
1540-
aliased_table_name, first_key,
1541-
aliased_join_table_name, second_key
1526+
first_key = through_reflection.klass.base_class.to_s.classify.foreign_key
1527+
second_key = options[:foreign_key] || primary_key
1528+
end
1529+
1530+
unless through_reflection.klass.descends_from_active_record?
1531+
jt_sti_extra = " AND %s.%s = %s" % [
1532+
aliased_join_table_name,
1533+
reflection.active_record.connection.quote_column_name(through_reflection.active_record.inheritance_column),
1534+
through_reflection.klass.quote_value(through_reflection.klass.name.demodulize)]
1535+
end
1536+
when :belongs_to
1537+
first_key = primary_key
1538+
if reflection.options[:source_type]
1539+
second_key = source_reflection.association_foreign_key
1540+
jt_source_extra = " AND %s.%s = %s" % [
1541+
aliased_join_table_name, reflection.active_record.connection.quote_column_name(reflection.source_reflection.options[:foreign_type]),
1542+
klass.quote_value(reflection.options[:source_type])
15421543
]
1544+
else
1545+
second_key = source_reflection.options[:foreign_key] || klass.to_s.classify.foreign_key
15431546
end
15441547
end
1548+
1549+
" LEFT OUTER JOIN %s ON (%s.%s = %s.%s%s%s%s) " % [
1550+
table_alias_for(through_reflection.klass.table_name, aliased_join_table_name),
1551+
parent.aliased_table_name, reflection.active_record.connection.quote_column_name(parent.primary_key),
1552+
aliased_join_table_name, reflection.active_record.connection.quote_column_name(jt_foreign_key),
1553+
jt_as_extra, jt_source_extra, jt_sti_extra
1554+
] +
1555+
" LEFT OUTER JOIN %s ON (%s.%s = %s.%s%s) " % [
1556+
table_name_and_alias,
1557+
aliased_table_name, reflection.active_record.connection.quote_column_name(first_key),
1558+
aliased_join_table_name, reflection.active_record.connection.quote_column_name(second_key),
1559+
as_extra
1560+
]
15451561

15461562
when reflection.macro == :has_many && reflection.options[:as]
15471563
" LEFT OUTER JOIN %s ON %s.%s = %s.%s AND %s.%s = %s" % [
@@ -1588,6 +1604,7 @@ def association_join
15881604
end
15891605

15901606
protected
1607+
15911608
def pluralize(table_name)
15921609
ActiveRecord::Base.pluralize_table_names ? table_name.to_s.pluralize : table_name
15931610
end

activerecord/lib/active_record/associations/has_many_through_association.rb

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,11 @@ def construct_owner_attributes(reflection)
138138

139139
# Construct attributes for :through pointing to owner and associate.
140140
def construct_join_attributes(associate)
141-
construct_owner_attributes(@reflection.through_reflection).merge(@reflection.source_reflection.primary_key_name => associate.id)
141+
returning construct_owner_attributes(@reflection.through_reflection).merge(@reflection.source_reflection.primary_key_name => associate.id) do |join_attributes|
142+
if @reflection.options[:source_type]
143+
join_attributes.merge!(@reflection.source_reflection.options[:foreign_type] => associate.class.base_class.name.to_s)
144+
end
145+
end
142146
end
143147

144148
# Associate attributes pointing to owner, quoted.
@@ -176,6 +180,12 @@ def construct_joins(custom_joins = nil)
176180
if @reflection.through_reflection.options[:as] || @reflection.source_reflection.macro == :belongs_to
177181
reflection_primary_key = @reflection.klass.primary_key
178182
source_primary_key = @reflection.source_reflection.primary_key_name
183+
if @reflection.options[:source_type]
184+
polymorphic_join = "AND %s.%s = %s" % [
185+
@reflection.through_reflection.table_name, "#{@reflection.source_reflection.options[:foreign_type]}",
186+
@owner.class.quote_value(@reflection.options[:source_type])
187+
]
188+
end
179189
else
180190
reflection_primary_key = @reflection.source_reflection.primary_key_name
181191
source_primary_key = @reflection.klass.primary_key

activerecord/lib/active_record/reflection.rb

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,8 +186,12 @@ def check_validity!
186186
if source_reflection.nil?
187187
raise HasManyThroughSourceAssociationNotFoundError.new(self)
188188
end
189+
190+
if options[:source_type] && source_reflection.options[:polymorphic].nil?
191+
raise HasManyThroughAssociationPointlessSourceTypeError.new(active_record.name, self, source_reflection)
192+
end
189193

190-
if source_reflection.options[:polymorphic]
194+
if source_reflection.options[:polymorphic] && options[:source_type].nil?
191195
raise HasManyThroughAssociationPolymorphicError.new(active_record.name, self, source_reflection)
192196
end
193197

@@ -205,7 +209,7 @@ def name_to_class_name(name)
205209
if options[:class_name]
206210
options[:class_name]
207211
elsif through_reflection # get the class_name of the belongs_to association of the through reflection
208-
source_reflection.class_name
212+
options[:source_type] || source_reflection.class_name
209213
else
210214
class_name = name.to_s.camelize
211215
class_name = class_name.singularize if [ :has_many, :has_and_belongs_to_many ].include?(macro)

activerecord/test/associations/join_model_test.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,18 @@ def test_has_many_polymorphic
300300
assert_equal [posts(:welcome), posts(:thinking)], tags(:general).taggings.find(:all, :include => :taggable)
301301
end
302302
end
303+
304+
def test_has_many_polymorphic_with_source_type
305+
assert_equal [posts(:welcome), posts(:thinking)], tags(:general).tagged_posts
306+
end
307+
308+
def test_eager_has_many_polymorphic_with_source_type
309+
tag_with_include = Tag.find(tags(:general).id, :include => :tagged_posts)
310+
desired = [posts(:welcome), posts(:thinking)]
311+
assert_no_queries do
312+
assert_equal desired, tag_with_include.tagged_posts
313+
end
314+
end
303315

304316
def test_has_many_through_has_many_find_all
305317
assert_equal comments(:greetings), authors(:david).comments.find(:all, :order => 'comments.id').first

activerecord/test/fixtures/tag.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,6 @@ class Tag < ActiveRecord::Base
22
has_many :taggings
33
has_many :taggables, :through => :taggings
44
has_one :tagging
5+
6+
has_many :tagged_posts, :through => :taggings, :source => :taggable, :source_type => 'Post'
57
end

0 commit comments

Comments
 (0)