aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--actionmailer/CHANGELOG.md36
-rw-r--r--activerecord/lib/active_record/associations/through_association.rb2
-rw-r--r--activerecord/lib/active_record/reflection.rb4
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb29
4 files changed, 70 insertions, 1 deletions
diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md
index 86ecb3ee88..68756c350d 100644
--- a/actionmailer/CHANGELOG.md
+++ b/actionmailer/CHANGELOG.md
@@ -1,3 +1,39 @@
+* Fix nested `has many :through` associations on unpersisted parent instances.
+
+ For example, if you have
+
+ class Post < ActiveRecord::Base
+ has_many :books, through: :author
+ has_many :subscriptions, through: :books
+ end
+
+ class Author < ActiveRecord::Base
+ has_one :post
+ has_many :books
+ has_many :subscriptions, through: :books
+ end
+
+ class Book < ActiveRecord::Base
+ belongs_to :author
+ has_many :subscriptions
+ end
+
+ class Subscription < ActiveRecord::Base
+ belongs_to :book
+ end
+
+ Before:
+ If `post` is not persisted, e.g `post = Post.new`, then `post.subscriptions`
+ will be empty no matter what.
+
+ After:
+ If `post` is not persisted, then `post.subscriptions` can be set and used
+ just like it would if `post` were persisted.
+
+ Fixes #16313.
+
+ *Zoltan Kiss*
+
* Add `assert_enqueued_emails` and `assert_no_enqueued_emails`.
Example:
diff --git a/activerecord/lib/active_record/associations/through_association.rb b/activerecord/lib/active_record/associations/through_association.rb
index af1bce523c..a276907dd3 100644
--- a/activerecord/lib/active_record/associations/through_association.rb
+++ b/activerecord/lib/active_record/associations/through_association.rb
@@ -77,7 +77,7 @@ module ActiveRecord
end
def foreign_key_present?
- through_reflection.belongs_to? && !owner[through_reflection.foreign_key].nil?
+ through_reflection.belongs_to_or_through? && !owner[through_reflection.foreign_key].nil?
end
def ensure_mutable
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 4265afc0a5..09d61b47a2 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -139,6 +139,10 @@ module ActiveRecord
klass.type_for_attribute(klass.primary_key)
end
+ def belongs_to_or_through?
+ belongs_to? || (through_reflection && through_reflection.belongs_to_or_through?)
+ end
+
# Returns the class name for the macro.
#
# <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>'Money'</tt>
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 5f52c65412..83365884f5 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -1166,4 +1166,33 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
post_through = organization.posts.build
assert_equal post_direct.author_id, post_through.author_id
end
+
+ def test_single_has_many_through_association_with_unpersisted_parent_instance
+ post_with_single_has_many_through = Class.new(Post) do
+ def self.name; 'PostWithSingleHasManyThrough'; end
+ has_many :subscriptions, through: :author
+ end
+ post = post_with_single_has_many_through.new
+ post.author = Author.create!(name: 'Federico Morissette')
+ book = Book.create!(name: 'essays on single has many through associations')
+ post.author.books << book
+ subscription = Subscription.first
+ book.subscriptions << subscription
+ assert_equal [subscription], post.subscriptions.to_a
+ end
+
+ def test_nested_has_many_through_association_with_unpersisted_parent_instance
+ post_with_nested_has_many_through = Class.new(Post) do
+ def self.name; 'PostWithNestedHasManyThrough'; end
+ has_many :books, through: :author
+ has_many :subscriptions, through: :books
+ end
+ post = post_with_nested_has_many_through.new
+ post.author = Author.create!(name: 'Obie Weissnat')
+ book = Book.create!(name: 'essays on nested single has many through associations')
+ post.author.books << book
+ subscription = Subscription.first
+ book.subscriptions << subscription
+ assert_equal [subscription], post.subscriptions.to_a
+ end
end