aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record/associations/through_association.rb
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib/active_record/associations/through_association.rb')
-rw-r--r--activerecord/lib/active_record/associations/through_association.rb41
1 files changed, 30 insertions, 11 deletions
diff --git a/activerecord/lib/active_record/associations/through_association.rb b/activerecord/lib/active_record/associations/through_association.rb
index c840a16160..8db8068295 100644
--- a/activerecord/lib/active_record/associations/through_association.rb
+++ b/activerecord/lib/active_record/associations/through_association.rb
@@ -1,12 +1,12 @@
module ActiveRecord
# = Active Record Through Association
module Associations
- module ThroughAssociation
+ module ThroughAssociation #:nodoc:
protected
def target_scope
- super & @reflection.through_reflection.klass.scoped
+ super.merge(@reflection.through_reflection.klass.scoped)
end
def association_scope
@@ -74,21 +74,40 @@ module ActiveRecord
right.create_on(right.create_and(conditions)))
end
- # Construct attributes for :through pointing to owner and associate.
- def construct_join_attributes(associate)
- # TODO: revisit this to allow it for deletion, supposing dependent option is supported
- raise ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(@owner, @reflection) if [:has_one, :has_many].include?(@reflection.source_reflection.macro)
+ # Construct attributes for :through pointing to owner and associate. This is used by the
+ # methods which create and delete records on the association.
+ #
+ # We only support indirectly modifying through associations which has a belongs_to source.
+ # This is the "has_many :tags, :through => :taggings" situation, where the join model
+ # typically has a belongs_to on both side. In other words, associations which could also
+ # be represented as has_and_belongs_to_many associations.
+ #
+ # We do not support creating/deleting records on the association where the source has
+ # some other type, because this opens up a whole can of worms, and in basically any
+ # situation it is more natural for the user to just create or modify their join records
+ # directly as required.
+ def construct_join_attributes(*records)
+ if @reflection.source_reflection.macro != :belongs_to
+ raise HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(@owner, @reflection)
+ end
join_attributes = {
@reflection.source_reflection.foreign_key =>
- associate.send(@reflection.source_reflection.association_primary_key)
+ records.map { |record|
+ record.send(@reflection.source_reflection.association_primary_key)
+ }
}
if @reflection.options[:source_type]
- join_attributes.merge!(@reflection.source_reflection.foreign_type => associate.class.base_class.name)
+ join_attributes[@reflection.source_reflection.foreign_type] =
+ records.map { |record| record.class.base_class.name }
end
- join_attributes
+ if records.count == 1
+ Hash[join_attributes.map { |k, v| [k, v.first] }]
+ else
+ join_attributes
+ end
end
# The reason that we are operating directly on the scope here (rather than passing
@@ -100,14 +119,14 @@ module ActiveRecord
scope = scope.where(@reflection.through_reflection.klass.send(:type_condition))
end
- scope = scope.where(@reflection.source_reflection.options[:conditions])
+ scope = scope.where(interpolate(@reflection.source_reflection.options[:conditions]))
scope.where(through_conditions)
end
# If there is a hash of conditions then we make sure the keys are scoped to the
# through table name if left ambiguous.
def through_conditions
- conditions = @reflection.through_reflection.options[:conditions]
+ conditions = interpolate(@reflection.through_reflection.options[:conditions])
if conditions.is_a?(Hash)
Hash[conditions.map { |key, value|