From 0a4e3f4a7f6d93c713389140bc6e6613b721b0ff Mon Sep 17 00:00:00 2001
From: Sean Griffin <sean@thoughtbot.com>
Date: Sun, 8 Jun 2014 16:04:03 -0600
Subject: Through associations should set both parent ids on join models

member = Member.new(club: Club.new)
member.save!

Before:

member.current_membership.club_id # => nil

After:

member.current_membership.club_id # => club's id
---
 activerecord/CHANGELOG.md                                |  5 +++++
 .../associations/has_many_through_association.rb         |  6 +++++-
 .../active_record/associations/through_association.rb    | 16 ++++++++++------
 .../associations/has_many_through_associations_test.rb   | 13 +++++++++++++
 .../associations/has_one_through_associations_test.rb    | 14 ++++++++++++++
 5 files changed, 47 insertions(+), 7 deletions(-)

(limited to 'activerecord')

diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 2915e9b42d..b3df23c623 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,8 @@
+*   Ensure both parent IDs are set on join records when both sides of a
+    through association are new.
+
+    *Sean Griffin*
+
 *   `ActiveRecord::Dirty` now detects in-place changes to mutable values.
     Serialized attributes on ActiveRecord models will no longer save when
     unchanged. Fixes #8328.
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 da9b125fd6..1713ab7ed3 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -180,7 +180,11 @@ module ActiveRecord
         def through_records_for(record)
           attributes = construct_join_attributes(record)
           candidates = Array.wrap(through_association.target)
-          candidates.find_all { |c| c.attributes.slice(*attributes.keys) == attributes }
+          candidates.find_all do |c|
+            attributes.all? do |key, value|
+              c.public_send(key) == value
+            end
+          end
         end
 
         def delete_through_records(records)
diff --git a/activerecord/lib/active_record/associations/through_association.rb b/activerecord/lib/active_record/associations/through_association.rb
index fcf3b219d4..f00fef8b9e 100644
--- a/activerecord/lib/active_record/associations/through_association.rb
+++ b/activerecord/lib/active_record/associations/through_association.rb
@@ -41,12 +41,16 @@ module ActiveRecord
         def construct_join_attributes(*records)
           ensure_mutable
 
-          join_attributes = {
-            source_reflection.foreign_key =>
-              records.map { |record|
-                record.send(source_reflection.association_primary_key(reflection.klass))
-              }
-          }
+          if source_reflection.association_primary_key(reflection.klass) == reflection.klass.primary_key
+            join_attributes = { source_reflection.name => records }
+          else
+            join_attributes = {
+              source_reflection.foreign_key =>
+                records.map { |record|
+                  record.send(source_reflection.association_primary_key(reflection.klass))
+                }
+            }
+          end
 
           if options[:source_type]
             join_attributes[source_reflection.foreign_type] =
diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb
index 2e62189e7a..8641584c0c 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -330,6 +330,19 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
     assert post.single_people.include?(person)
   end
 
+  def test_both_parent_ids_set_when_saving_new
+    post = Post.new(title: 'Hello', body: 'world')
+    person = Person.new(first_name: 'Sean')
+
+    post.people = [person]
+    post.save
+
+    assert post.id
+    assert person.id
+    assert_equal post.id, post.readers.first.post_id
+    assert_equal person.id, post.readers.first.person_id
+  end
+
   def test_delete_association
     assert_queries(2){posts(:welcome);people(:michael); }
 
diff --git a/activerecord/test/cases/associations/has_one_through_associations_test.rb b/activerecord/test/cases/associations/has_one_through_associations_test.rb
index a2725441b3..089cb0a3a2 100644
--- a/activerecord/test/cases/associations/has_one_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_through_associations_test.rb
@@ -45,6 +45,20 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
     assert_equal clubs(:moustache_club), new_member.club
   end
 
+  def test_creating_association_sets_both_parent_ids_for_new
+    member = Member.new(name: 'Sean Griffin')
+    club = Club.new(name: 'Da Club')
+
+    member.club = club
+
+    member.save!
+
+    assert member.id
+    assert club.id
+    assert_equal member.id, member.current_membership.member_id
+    assert_equal club.id, member.current_membership.club_id
+  end
+
   def test_replace_target_record
     new_club = Club.create(:name => "Marx Bros")
     @member.club = new_club
-- 
cgit v1.2.3