Take the 2008 Git User's Survey and help out! [ hide ]

public
Fork of rails/rails
Description: Ruby on Rails
Homepage: http://rubyonrails.org
Clone URL: git://github.com/ddollar/rails.git
Search Repo:
apply [6408] to stable

git-svn-id: 
http://svn-commit.rubyonrails.org/rails/branches/1-2-stable@6410 
5ecf4fe2-1ee6-0310-87b1-e25e094e27de
technoweenie (author)
Mon Mar 12 22:32:55 -0700 2007
commit  daa17995b55f092b89eef63223bc16d16fcee4b4
tree    ac427c1deb9993a10f6cf26e0da4e6cb0da309d3
parent  736cca87dc092ee850296f191bab990cb2fc80e9
...
1
2
 
 
3
4
5
...
1
2
3
4
5
6
7
0
@@ -1,5 +1,7 @@
0
 *1.15.3* (March 12th, 2007)
0
 
0
+* Allow a polymorphic :source for has_many :through associations. Closes #7143 [protocool]
0
+
0
 * Consistently quote primary key column names. #7763 [toolmantim]
0
 
0
 * Fixtures: fix YAML ordered map support. #2665 [Manuel Holtgrewe, nfbuckley]
...
20
21
22
23
 
 
 
 
 
 
 
24
25
26
...
529
530
531
 
 
532
533
534
...
1087
1088
1089
1090
 
1091
1092
1093
...
1491
1492
1493
 
 
 
 
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
 
 
 
 
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
 
 
 
 
 
 
 
 
 
 
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1542
 
 
1543
1544
 
 
 
 
 
 
 
 
 
 
 
 
 
1545
1546
1547
...
1588
1589
1590
 
1591
1592
1593
...
20
21
22
 
23
24
25
26
27
28
29
30
31
32
...
535
536
537
538
539
540
541
542
...
1095
1096
1097
 
1098
1099
1100
1101
...
1499
1500
1501
1502
1503
1504
1505
1506
 
 
 
 
 
 
 
 
 
 
1507
1508
1509
1510
1511
1512
 
 
 
 
 
 
 
 
 
 
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
...
1604
1605
1606
1607
1608
1609
1610
0
@@ -20,7 +20,13 @@ module ActiveRecord
0
       super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' on the polymorphic object '#{source_reflection.class_name}##{source_reflection.name}'.")
0
     end
0
   end
0
-
0
+
0
+ class HasManyThroughAssociationPointlessSourceTypeError < ActiveRecordError #:nodoc:
0
+ def initialize(owner_class_name, reflection, source_reflection)
0
+ 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.")
0
+ end
0
+ end
0
+
0
   class HasManyThroughSourceAssociationNotFoundError < ActiveRecordError #:nodoc:
0
     def initialize(reflection)
0
       through_reflection = reflection.through_reflection
0
@@ -529,6 +535,8 @@ module ActiveRecord
0
       # * <tt>:source</tt>: Specifies the source association name used by <tt>has_many :through</tt> queries. Only use it if the name cannot be
0
       # inferred from the association. <tt>has_many :subscribers, :through => :subscriptions</tt> will look for either +:subscribers+ or
0
       # +:subscriber+ on +Subscription+, unless a +:source+ is given.
0
+ # * <tt>:source_type</tt>: Specifies type of the source association used by <tt>has_many :through</tt> queries where the source association
0
+ # is a polymorphic belongs_to.
0
       # * <tt>:uniq</tt> - if set to true, duplicates will be omitted from the collection. Useful in conjunction with :through.
0
       #
0
       # Option examples:
0
@@ -1087,7 +1095,7 @@ module ActiveRecord
0
             :class_name, :table_name, :foreign_key,
0
             :exclusively_dependent, :dependent,
0
             :select, :conditions, :include, :order, :group, :limit, :offset,
0
- :as, :through, :source,
0
+ :as, :through, :source, :source_type,
0
             :uniq,
0
             :finder_sql, :counter_sql,
0
             :before_add, :after_add, :before_remove, :after_remove,
0
@@ -1491,57 +1499,65 @@ module ActiveRecord
0
                   case
0
                     when reflection.macro == :has_many && reflection.options[:through]
0
                       through_conditions = through_reflection.options[:conditions] ? "AND #{interpolate_sql(sanitize_sql(through_reflection.options[:conditions]))}" : ''
0
+
0
+ jt_foreign_key = jt_as_extra = jt_source_extra = jt_sti_extra = nil
0
+ first_key = second_key = as_extra = nil
0
+
0
                       if through_reflection.options[:as] # has_many :through against a polymorphic join
0
- polymorphic_foreign_key = through_reflection.options[:as].to_s + '_id'
0
- polymorphic_foreign_type = through_reflection.options[:as].to_s + '_type'
0
-
0
- " LEFT OUTER JOIN %s ON (%s.%s = %s.%s AND %s.%s = %s) " % [
0
- table_alias_for(through_reflection.klass.table_name, aliased_join_table_name),
0
- aliased_join_table_name, polymorphic_foreign_key,
0
- parent.aliased_table_name, parent.primary_key,
0
- aliased_join_table_name, polymorphic_foreign_type, klass.quote_value(parent.active_record.base_class.name)] +
0
- " LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [table_name_and_alias,
0
- aliased_table_name, primary_key, aliased_join_table_name, options[:foreign_key] || reflection.klass.to_s.classify.foreign_key
0
+ jt_foreign_key = through_reflection.options[:as].to_s + '_id'
0
+ jt_as_extra = " AND %s.%s = %s" % [
0
+ aliased_join_table_name, reflection.active_record.connection.quote_column_name(through_reflection.options[:as].to_s + '_type'),
0
+ klass.quote_value(parent.active_record.base_class.name)
0
                         ]
0
                       else
0
- if source_reflection.macro == :has_many && source_reflection.options[:as]
0
- " LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [
0
- table_alias_for(through_reflection.klass.table_name, aliased_join_table_name), aliased_join_table_name,
0
- through_reflection.primary_key_name,
0
- parent.aliased_table_name, parent.primary_key] +
0
- " LEFT OUTER JOIN %s ON %s.%s = %s.%s AND %s.%s = %s " % [
0
- table_name_and_alias,
0
- aliased_table_name, "#{source_reflection.options[:as]}_id",
0
- aliased_join_table_name, options[:foreign_key] || primary_key,
0
- aliased_table_name, "#{source_reflection.options[:as]}_type",
0
+ jt_foreign_key = through_reflection.primary_key_name
0
+ end
0
+
0
+ case source_reflection.macro
0
+ when :has_many
0
+ if source_reflection.options[:as]
0
+ first_key = "#{source_reflection.options[:as]}_id"
0
+ second_key = options[:foreign_key] || primary_key
0
+ as_extra = " AND %s.%s = %s" % [
0
+ aliased_table_name, reflection.active_record.connection.quote_column_name("#{source_reflection.options[:as]}_type"),
0
                             klass.quote_value(source_reflection.active_record.base_class.name)
0
                           ]
0
                         else
0
- case source_reflection.macro
0
- when :belongs_to
0
- first_key = primary_key
0
- second_key = source_reflection.options[:foreign_key] || klass.to_s.classify.foreign_key
0
- extra = nil
0
- when :has_many
0
- first_key = through_reflection.klass.base_class.to_s.classify.foreign_key
0
- second_key = options[:foreign_key] || primary_key
0
- extra = through_reflection.klass.descends_from_active_record? ? nil :
0
- " AND %s.%s = %s" % [
0
- aliased_join_table_name,
0
- reflection.active_record.connection.quote_column_name(through_reflection.active_record.inheritance_column),
0
- through_reflection.klass.quote_value(through_reflection.klass.name.demodulize)]
0
- end
0
- " LEFT OUTER JOIN %s ON (%s.%s = %s.%s%s) " % [
0
- table_alias_for(through_reflection.klass.table_name, aliased_join_table_name),
0
- aliased_join_table_name, through_reflection.primary_key_name,
0
- parent.aliased_table_name, parent.primary_key, extra] +
0
- " LEFT OUTER JOIN %s ON (%s.%s = %s.%s) " % [
0
- table_name_and_alias,
0
- aliased_table_name, first_key,
0
- aliased_join_table_name, second_key
0
+ first_key = through_reflection.klass.base_class.to_s.classify.foreign_key
0
+ second_key = options[:foreign_key] || primary_key
0
+ end
0
+
0
+ unless through_reflection.klass.descends_from_active_record?
0
+ jt_sti_extra = " AND %s.%s = %s" % [
0
+ aliased_join_table_name,
0
+ reflection.active_record.connection.quote_column_name(through_reflection.active_record.inheritance_column),
0
+ through_reflection.klass.quote_value(through_reflection.klass.name.demodulize)]
0
+ end
0
+ when :belongs_to
0
+ first_key = primary_key
0
+ if reflection.options[:source_type]
0
+ second_key = source_reflection.association_foreign_key
0
+ jt_source_extra = " AND %s.%s = %s" % [
0
+ aliased_join_table_name, reflection.active_record.connection.quote_column_name(reflection.source_reflection.options[:foreign_type]),
0
+ klass.quote_value(reflection.options[:source_type])
0
                           ]
0
+ else
0
+ second_key = source_reflection.options[:foreign_key] || klass.to_s.classify.foreign_key
0
                         end
0
                       end
0
+
0
+ " LEFT OUTER JOIN %s ON (%s.%s = %s.%s%s%s%s) " % [
0
+ table_alias_for(through_reflection.klass.table_name, aliased_join_table_name),
0
+ parent.aliased_table_name, reflection.active_record.connection.quote_column_name(parent.primary_key),
0
+ aliased_join_table_name, reflection.active_record.connection.quote_column_name(jt_foreign_key),
0
+ jt_as_extra, jt_source_extra, jt_sti_extra
0
+ ] +
0
+ " LEFT OUTER JOIN %s ON (%s.%s = %s.%s%s) " % [
0
+ table_name_and_alias,
0
+ aliased_table_name, reflection.active_record.connection.quote_column_name(first_key),
0
+ aliased_join_table_name, reflection.active_record.connection.quote_column_name(second_key),
0
+ as_extra
0
+ ]
0
                     
0
                     when reflection.macro == :has_many && reflection.options[:as]
0
                       " LEFT OUTER JOIN %s ON %s.%s = %s.%s AND %s.%s = %s" % [
0
@@ -1588,6 +1604,7 @@ module ActiveRecord
0
             end
0
             
0
             protected
0
+
0
               def pluralize(table_name)
0
                 ActiveRecord::Base.pluralize_table_names ? table_name.to_s.pluralize : table_name
0
               end
...
138
139
140
141
 
 
 
 
 
142
143
144
...
176
177
178
 
 
 
 
 
 
179
180
181
...
138
139
140
 
141
142
143
144
145
146
147
148
...
180
181
182
183
184
185
186
187
188
189
190
191
0
@@ -138,7 +138,11 @@ module ActiveRecord
0
 
0
         # Construct attributes for :through pointing to owner and associate.
0
         def construct_join_attributes(associate)
0
- construct_owner_attributes(@reflection.through_reflection).merge(@reflection.source_reflection.primary_key_name => associate.id)
0
+ returning construct_owner_attributes(@reflection.through_reflection).merge(@reflection.source_reflection.primary_key_name => associate.id) do |join_attributes|
0
+ if @reflection.options[:source_type]
0
+ join_attributes.merge!(@reflection.source_reflection.options[:foreign_type] => associate.class.base_class.name.to_s)
0
+ end
0
+ end
0
         end
0
 
0
         # Associate attributes pointing to owner, quoted.
0
@@ -176,6 +180,12 @@ module ActiveRecord
0
           if @reflection.through_reflection.options[:as] || @reflection.source_reflection.macro == :belongs_to
0
             reflection_primary_key = @reflection.klass.primary_key
0
             source_primary_key = @reflection.source_reflection.primary_key_name
0
+ if @reflection.options[:source_type]
0
+ polymorphic_join = "AND %s.%s = %s" % [
0
+ @reflection.through_reflection.table_name, "#{@reflection.source_reflection.options[:foreign_type]}",
0
+ @owner.class.quote_value(@reflection.options[:source_type])
0
+ ]
0
+ end
0
           else
0
             reflection_primary_key = @reflection.source_reflection.primary_key_name
0
             source_primary_key = @reflection.klass.primary_key
...
186
187
188
 
 
 
 
189
190
 
191
192
193
...
205
206
207
208
 
209
210
211
...
186
187
188
189
190
191
192
193
 
194
195
196
197
...
209
210
211
 
212
213
214
215
0
@@ -186,8 +186,12 @@ module ActiveRecord
0
           if source_reflection.nil?
0
             raise HasManyThroughSourceAssociationNotFoundError.new(self)
0
           end
0
+
0
+ if options[:source_type] && source_reflection.options[:polymorphic].nil?
0
+ raise HasManyThroughAssociationPointlessSourceTypeError.new(active_record.name, self, source_reflection)
0
+ end
0
           
0
- if source_reflection.options[:polymorphic]
0
+ if source_reflection.options[:polymorphic] && options[:source_type].nil?
0
             raise HasManyThroughAssociationPolymorphicError.new(active_record.name, self, source_reflection)
0
           end
0
           
0
@@ -205,7 +209,7 @@ module ActiveRecord
0
             if options[:class_name]
0
               options[:class_name]
0
             elsif through_reflection # get the class_name of the belongs_to association of the through reflection
0
- source_reflection.class_name
0
+ options[:source_type] || source_reflection.class_name
0
             else
0
               class_name = name.to_s.camelize
0
               class_name = class_name.singularize if [ :has_many, :has_and_belongs_to_many ].include?(macro)
...
300
301
302
 
 
 
 
 
 
 
 
 
 
 
 
303
304
305
...
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
0
@@ -300,6 +300,18 @@ class AssociationsJoinModelTest < Test::Unit::TestCase
0
       assert_equal [posts(:welcome), posts(:thinking)], tags(:general).taggings.find(:all, :include => :taggable)
0
     end
0
   end
0
+
0
+ def test_has_many_polymorphic_with_source_type
0
+ assert_equal [posts(:welcome), posts(:thinking)], tags(:general).tagged_posts
0
+ end
0
+
0
+ def test_eager_has_many_polymorphic_with_source_type
0
+ tag_with_include = Tag.find(tags(:general).id, :include => :tagged_posts)
0
+ desired = [posts(:welcome), posts(:thinking)]
0
+ assert_no_queries do
0
+ assert_equal desired, tag_with_include.tagged_posts
0
+ end
0
+ end
0
 
0
   def test_has_many_through_has_many_find_all
0
     assert_equal comments(:greetings), authors(:david).comments.find(:all, :order => 'comments.id').first
...
2
3
4
 
 
5
6
...
2
3
4
5
6
7
8
0
@@ -2,4 +2,6 @@ class Tag < ActiveRecord::Base
0
   has_many :taggings
0
   has_many :taggables, :through => :taggings
0
   has_one :tagging
0
+
0
+ has_many :tagged_posts, :through => :taggings, :source => :taggable, :source_type => 'Post'
0
 end
0
\ No newline at end of file

Comments

    No one has commented yet.