aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib
diff options
context:
space:
mode:
authorRick Olson <technoweenie@gmail.com>2007-03-13 05:23:18 +0000
committerRick Olson <technoweenie@gmail.com>2007-03-13 05:23:18 +0000
commite3dab67c44ed047cc91ccfcc17c5ee2bb9997be3 (patch)
tree5acc906178f850d70706a24c5ca47a3acdd43097 /activerecord/lib
parent2823a56f14edac7e9012484d375f23c89405d725 (diff)
downloadrails-e3dab67c44ed047cc91ccfcc17c5ee2bb9997be3.tar.gz
rails-e3dab67c44ed047cc91ccfcc17c5ee2bb9997be3.tar.bz2
rails-e3dab67c44ed047cc91ccfcc17c5ee2bb9997be3.zip
Allow a polymorphic :source for has_many :through associations. Closes #7143 [protocool]
git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@6408 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
Diffstat (limited to 'activerecord/lib')
-rwxr-xr-xactiverecord/lib/active_record/associations.rb156
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb12
-rw-r--r--activerecord/lib/active_record/reflection.rb8
3 files changed, 127 insertions, 49 deletions
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index f0e5608b05..6fb00152a1 100755
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -20,7 +20,13 @@ module ActiveRecord
super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' on the polymorphic object '#{source_reflection.class_name}##{source_reflection.name}'.")
end
end
-
+
+ class HasManyThroughAssociationPointlessSourceTypeError < ActiveRecordError #:nodoc:
+ def initialize(owner_class_name, reflection, source_reflection)
+ 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.")
+ end
+ end
+
class HasManyThroughSourceAssociationNotFoundError < ActiveRecordError #:nodoc:
def initialize(reflection)
through_reflection = reflection.through_reflection
@@ -593,6 +599,8 @@ module ActiveRecord
# * <tt>:source</tt>: Specifies the source association name used by <tt>has_many :through</tt> queries. Only use it if the name cannot be
# inferred from the association. <tt>has_many :subscribers, :through => :subscriptions</tt> will look for either +:subscribers+ or
# +:subscriber+ on +Subscription+, unless a +:source+ is given.
+ # * <tt>:source_type</tt>: Specifies type of the source association used by <tt>has_many :through</tt> queries where the source association
+ # is a polymorphic belongs_to.
# * <tt>:uniq</tt> - if set to true, duplicates will be omitted from the collection. Useful in conjunction with :through.
#
# Option examples:
@@ -1151,7 +1159,7 @@ module ActiveRecord
:class_name, :table_name, :foreign_key,
:exclusively_dependent, :dependent,
:select, :conditions, :include, :order, :group, :limit, :offset,
- :as, :through, :source,
+ :as, :through, :source, :source_type,
:uniq,
:finder_sql, :counter_sql,
:before_add, :after_add, :before_remove, :after_remove,
@@ -1555,58 +1563,113 @@ module ActiveRecord
case
when reflection.macro == :has_many && reflection.options[:through]
through_conditions = through_reflection.options[:conditions] ? "AND #{interpolate_sql(sanitize_sql(through_reflection.options[:conditions]))}" : ''
+
+ jt_foreign_key = jt_as_extra = jt_source_extra = jt_sti_extra = nil
+ first_key = second_key = as_extra = nil
+
if through_reflection.options[:as] # has_many :through against a polymorphic join
- polymorphic_foreign_key = through_reflection.options[:as].to_s + '_id'
- polymorphic_foreign_type = through_reflection.options[:as].to_s + '_type'
-
- " LEFT OUTER JOIN %s ON (%s.%s = %s.%s AND %s.%s = %s) " % [
- table_alias_for(through_reflection.klass.table_name, aliased_join_table_name),
- aliased_join_table_name, polymorphic_foreign_key,
- parent.aliased_table_name, parent.primary_key,
- aliased_join_table_name, polymorphic_foreign_type, klass.quote_value(parent.active_record.base_class.name)] +
- " LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [table_name_and_alias,
- aliased_table_name, primary_key, aliased_join_table_name, options[:foreign_key] || reflection.klass.to_s.foreign_key
+ ####polymorphic_foreign_key = through_reflection.options[:as].to_s + '_id'
+ ####polymorphic_foreign_type = through_reflection.options[:as].to_s + '_type'
+ ####
+ ####" LEFT OUTER JOIN %s ON (%s.%s = %s.%s AND %s.%s = %s) " % [
+ #### table_alias_for(through_reflection.klass.table_name, aliased_join_table_name),
+ #### aliased_join_table_name, polymorphic_foreign_key,
+ #### parent.aliased_table_name, parent.primary_key,
+ #### aliased_join_table_name, polymorphic_foreign_type, klass.quote_value(parent.active_record.base_class.name)] +
+ ####" LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [table_name_and_alias,
+ #### aliased_table_name, primary_key, aliased_join_table_name, options[:foreign_key] || reflection.klass.to_s.foreign_key
+ jt_foreign_key = through_reflection.options[:as].to_s + '_id'
+ jt_as_extra = " AND %s.%s = %s" % [
+ aliased_join_table_name,
+ reflection.active_record.connection.quote_column_name(through_reflection.options[:as].to_s + '_type'),
+ klass.quote_value(parent.active_record.base_class.name)
]
else
- if source_reflection.macro == :has_many && source_reflection.options[:as]
- " LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [
- table_alias_for(through_reflection.klass.table_name, aliased_join_table_name), aliased_join_table_name,
- through_reflection.primary_key_name,
- parent.aliased_table_name, parent.primary_key] +
- " LEFT OUTER JOIN %s ON %s.%s = %s.%s AND %s.%s = %s " % [
- table_name_and_alias,
- aliased_table_name, "#{source_reflection.options[:as]}_id",
- aliased_join_table_name, options[:foreign_key] || primary_key,
- aliased_table_name, "#{source_reflection.options[:as]}_type",
- klass.quote_value(source_reflection.active_record.base_class.name)
+ ##if source_reflection.macro == :has_many && source_reflection.options[:as]
+ ####" LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [
+ #### table_alias_for(through_reflection.klass.table_name, aliased_join_table_name), aliased_join_table_name,
+ #### through_reflection.primary_key_name,
+ #### parent.aliased_table_name, parent.primary_key] +
+ ####" LEFT OUTER JOIN %s ON %s.%s = %s.%s AND %s.%s = %s " % [
+ #### table_name_and_alias,
+ #### aliased_table_name, "#{source_reflection.options[:as]}_id",
+ #### aliased_join_table_name, options[:foreign_key] || primary_key,
+ #### aliased_table_name, "#{source_reflection.options[:as]}_type",
+ #### klass.quote_value(source_reflection.active_record.base_class.name)
+ jt_foreign_key = through_reflection.primary_key_name
+ end
+
+ case source_reflection.macro
+ when :has_many
+ if source_reflection.options[:as]
+ first_key = "#{source_reflection.options[:as]}_id"
+ second_key = options[:foreign_key] || primary_key
+ as_extra = " AND %s.%s = %s" % [
+ aliased_table_name,
+ reflection.active_record.connection.quote_column_name("#{source_reflection.options[:as]}_type"),
+ klass.quote_value(source_reflection.active_record.base_class.name)
]
else
- case source_reflection.macro
- when :belongs_to
- first_key = primary_key
- second_key = source_reflection.options[:foreign_key] || klass.to_s.foreign_key
- extra = nil
- when :has_many
- first_key = through_reflection.klass.base_class.to_s.foreign_key
- second_key = options[:foreign_key] || primary_key
- extra = through_reflection.klass.descends_from_active_record? ? nil :
- " AND %s.%s = %s" % [
- aliased_join_table_name,
- reflection.active_record.connection.quote_column_name(through_reflection.active_record.inheritance_column),
- through_reflection.klass.quote_value(through_reflection.klass.name.demodulize)]
- end
- " LEFT OUTER JOIN %s ON (%s.%s = %s.%s%s) " % [
- table_alias_for(through_reflection.klass.table_name, aliased_join_table_name),
- aliased_join_table_name, through_reflection.primary_key_name,
- parent.aliased_table_name, parent.primary_key, extra] +
- " LEFT OUTER JOIN %s ON (%s.%s = %s.%s) " % [
- table_name_and_alias,
- aliased_table_name, first_key,
- aliased_join_table_name, second_key
+ ####case source_reflection.macro
+ #### when :belongs_to
+ #### first_key = primary_key
+ #### second_key = source_reflection.options[:foreign_key] || klass.to_s.foreign_key
+ #### extra = nil
+ #### when :has_many
+ #### first_key = through_reflection.klass.base_class.to_s.foreign_key
+ #### second_key = options[:foreign_key] || primary_key
+ #### extra = through_reflection.klass.descends_from_active_record? ? nil :
+ #### " AND %s.%s = %s" % [
+ #### aliased_join_table_name,
+ #### reflection.active_record.connection.quote_column_name(through_reflection.active_record.inheritance_column),
+ #### through_reflection.klass.quote_value(through_reflection.klass.name.demodulize)]
+ ####end
+ ####" LEFT OUTER JOIN %s ON (%s.%s = %s.%s%s) " % [
+ #### table_alias_for(through_reflection.klass.table_name, aliased_join_table_name),
+ #### aliased_join_table_name, through_reflection.primary_key_name,
+ #### parent.aliased_table_name, parent.primary_key, extra] +
+ ####" LEFT OUTER JOIN %s ON (%s.%s = %s.%s) " % [
+ #### table_name_and_alias,
+ #### aliased_table_name, first_key,
+ #### aliased_join_table_name, second_key
+ ####]
+ first_key = through_reflection.klass.base_class.to_s.foreign_key
+ second_key = options[:foreign_key] || primary_key
+ end
+
+ unless through_reflection.klass.descends_from_active_record?
+ jt_sti_extra = " AND %s.%s = %s" % [
+ aliased_join_table_name,
+ reflection.active_record.connection.quote_column_name(through_reflection.active_record.inheritance_column),
+ through_reflection.klass.quote_value(through_reflection.klass.name.demodulize)]
+ end
+ when :belongs_to
+ first_key = primary_key
+ if reflection.options[:source_type]
+ second_key = source_reflection.association_foreign_key
+ jt_source_extra = " AND %s.%s = %s" % [
+ aliased_join_table_name,
+ reflection.active_record.connection.quote_column_name(reflection.source_reflection.options[:foreign_type]),
+ klass.quote_value(reflection.options[:source_type])
]
+ else
+ second_key = source_reflection.options[:foreign_key] || klass.to_s.foreign_key
end
end
-
+
+ " LEFT OUTER JOIN %s ON (%s.%s = %s.%s%s%s%s) " % [
+ table_alias_for(through_reflection.klass.table_name, aliased_join_table_name),
+ parent.aliased_table_name, reflection.active_record.connection.quote_column_name(parent.primary_key),
+ aliased_join_table_name, reflection.active_record.connection.quote_column_name(jt_foreign_key),
+ jt_as_extra, jt_source_extra, jt_sti_extra
+ ] +
+ " LEFT OUTER JOIN %s ON (%s.%s = %s.%s%s) " % [
+ table_name_and_alias,
+ aliased_table_name, reflection.active_record.connection.quote_column_name(first_key),
+ aliased_join_table_name, reflection.active_record.connection.quote_column_name(second_key),
+ as_extra
+ ]
+
when reflection.macro == :has_many && reflection.options[:as]
" LEFT OUTER JOIN %s ON %s.%s = %s.%s AND %s.%s = %s" % [
table_name_and_alias,
@@ -1652,6 +1715,7 @@ module ActiveRecord
end
protected
+
def pluralize(table_name)
ActiveRecord::Base.pluralize_table_names ? table_name.to_s.pluralize : table_name
end
diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb
index 26559d523a..a8ad52bef9 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -138,7 +138,11 @@ module ActiveRecord
# Construct attributes for :through pointing to owner and associate.
def construct_join_attributes(associate)
- construct_owner_attributes(@reflection.through_reflection).merge(@reflection.source_reflection.primary_key_name => associate.id)
+ returning construct_owner_attributes(@reflection.through_reflection).merge(@reflection.source_reflection.primary_key_name => associate.id) do |join_attributes|
+ if @reflection.options[:source_type]
+ join_attributes.merge!(@reflection.source_reflection.options[:foreign_type] => associate.class.base_class.name.to_s)
+ end
+ end
end
# Associate attributes pointing to owner, quoted.
@@ -176,6 +180,12 @@ module ActiveRecord
if @reflection.through_reflection.options[:as] || @reflection.source_reflection.macro == :belongs_to
reflection_primary_key = @reflection.klass.primary_key
source_primary_key = @reflection.source_reflection.primary_key_name
+ if @reflection.options[:source_type]
+ polymorphic_join = "AND %s.%s = %s" % [
+ @reflection.through_reflection.table_name, "#{@reflection.source_reflection.options[:foreign_type]}",
+ @owner.class.quote_value(@reflection.options[:source_type])
+ ]
+ end
else
reflection_primary_key = @reflection.source_reflection.primary_key_name
source_primary_key = @reflection.klass.primary_key
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 2adadb29c5..62184eeff2 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -178,8 +178,12 @@ module ActiveRecord
if source_reflection.nil?
raise HasManyThroughSourceAssociationNotFoundError.new(self)
end
+
+ if options[:source_type] && source_reflection.options[:polymorphic].nil?
+ raise HasManyThroughAssociationPointlessSourceTypeError.new(active_record.name, self, source_reflection)
+ end
- if source_reflection.options[:polymorphic]
+ if source_reflection.options[:polymorphic] && options[:source_type].nil?
raise HasManyThroughAssociationPolymorphicError.new(active_record.name, self, source_reflection)
end
@@ -193,7 +197,7 @@ module ActiveRecord
def derive_class_name
# get the class_name of the belongs_to association of the through reflection
if through_reflection
- source_reflection.class_name
+ options[:source_type] || source_reflection.class_name
else
class_name = name.to_s.camelize
class_name = class_name.singularize if [ :has_many, :has_and_belongs_to_many ].include?(macro)