aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
authorRyuta Kamizono <kamipo@gmail.com>2018-01-10 20:25:37 +0900
committerRyuta Kamizono <kamipo@gmail.com>2018-01-10 20:28:37 +0900
commit027f865fc8b262d9ba3ee51da3483e94a5489b66 (patch)
treefdac6eeb639beafed739ea9ddf1b940f9219233c /activerecord
parent7ccd070c91ae1089f307c8557dcbd9a14837316c (diff)
parent1813350f0927dde01a11ebbd33a8f6b0deacd073 (diff)
downloadrails-027f865fc8b262d9ba3ee51da3483e94a5489b66.tar.gz
rails-027f865fc8b262d9ba3ee51da3483e94a5489b66.tar.bz2
rails-027f865fc8b262d9ba3ee51da3483e94a5489b66.zip
Merge pull request #16314 from zoltankiss/allow-nested-has-many-associations-on-unpersisted-parent-instances
fix nested `has many :through` associations on unpersisted parent instances
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG.md38
-rw-r--r--activerecord/lib/active_record/associations/through_association.rb2
-rw-r--r--activerecord/lib/active_record/reflection.rb8
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb29
4 files changed, 76 insertions, 1 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 167bd8c82c..3393534eb3 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,41 @@
+* Fix nested `has_many :through` associations on unpersisted parent instances.
+
+ For example, if you have
+
+ class Post < ActiveRecord::Base
+ belongs_to :author
+ 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, then `post.subscriptions` will be empty.
+
+ 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*
+
* Fixed inconsistency with `first(n)` when used with `limit()`.
The `first(n)` finder now respects the `limit()`, making it consistent
with `relation.to_a.first(n)`, and also with the behavior of `last(n)`.
diff --git a/activerecord/lib/active_record/associations/through_association.rb b/activerecord/lib/active_record/associations/through_association.rb
index 249eb1775b..806397f83a 100644
--- a/activerecord/lib/active_record/associations/through_association.rb
+++ b/activerecord/lib/active_record/associations/through_association.rb
@@ -68,7 +68,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 9e32b69786..7b53f9c15c 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -504,6 +504,10 @@ module ActiveRecord
@association_scope_cache.clear
end
+ def belongs_to_or_through?
+ belongs_to?
+ end
+
def nested?
false
end
@@ -836,6 +840,10 @@ module ActiveRecord
source_reflection.join_scopes(table, predicate_builder) + super
end
+ def belongs_to_or_through?
+ through_reflection.belongs_to_or_through?
+ end
+
def has_scope?
scope || options[:source_type] ||
source_reflection.has_scope? ||
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 7c9c9e81ab..a19cd9d365 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -1308,6 +1308,35 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
end
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 has many through associations")
+ post.author.books << book
+ subscription = Subscription.first
+ book.subscriptions << subscription
+ assert_equal [subscription], post.subscriptions.to_a
+ end
+
private
def make_model(name)
Class.new(ActiveRecord::Base) { define_singleton_method(:name) { name } }