Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge pull request #16314 from zoltankiss/allow-nested-has-many-assoc…
…iations-on-unpersisted-parent-instances

fix nested `has many :through` associations on unpersisted parent instances
  • Loading branch information
kamipo committed Jan 10, 2018
2 parents 7ccd070 + 1813350 commit 027f865
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 1 deletion.
38 changes: 38 additions & 0 deletions 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)`.
Expand Down
Expand Up @@ -68,7 +68,7 @@ def stale_state
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
Expand Down
8 changes: 8 additions & 0 deletions activerecord/lib/active_record/reflection.rb
Expand Up @@ -504,6 +504,10 @@ def clear_association_scope_cache # :nodoc:
@association_scope_cache.clear
end

def belongs_to_or_through?
belongs_to?
end

def nested?
false
end
Expand Down Expand Up @@ -836,6 +840,10 @@ def join_scopes(table, predicate_builder) # :nodoc:
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? ||
Expand Down
Expand Up @@ -1308,6 +1308,35 @@ def test_incorrectly_ordered_through_associations
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 } }
Expand Down

0 comments on commit 027f865

Please sign in to comment.