diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index e954bd2fb7fb4..93f041b2ac547 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,7 +1,13 @@ ## Rails 5.0.0.rc1 (May 06, 2016) ## -* No changes. +* Merge parent joins for through associations. + Merges parent associations joins for through associations into a scope. Previously joins in association scope were + taken into account only for root associations with that scope. + + Fixes #22538. + + *Seva Orlov* ## Rails 5.0.0.beta4 (April 27, 2016) ## diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb index 48437a1c9e72a..ccf3f077f1e6e 100644 --- a/activerecord/lib/active_record/associations/association_scope.rb +++ b/activerecord/lib/active_record/associations/association_scope.rb @@ -134,13 +134,13 @@ def add_constraints(scope, owner, association_klass, refl, chain_head, chain_tai scope = next_chain_scope(scope, table, reflection, association_klass, foreign_table, next_reflection) end - # Exclude the scope of the association itself, because that - # was already merged in the #scope method. reflection.constraints.each do |scope_chain_item| - item = eval_scope(reflection.klass, scope_chain_item, owner) + item = eval_scope(reflection.klass, scope_chain_item, owner) if scope_chain_item == refl.scope scope.merge! item.except(:where, :includes) + elsif item.joins_values.any? + merge_joins(scope, item) end reflection.all_includes do @@ -158,6 +158,21 @@ def add_constraints(scope, owner, association_klass, refl, chain_head, chain_tai scope end + # Merges inner joins from `other` relation to `scope` relation. + def merge_joins(scope, other) + association_joins, rest_joins = other.joins_values.partition do |join| + case join + when Hash, Symbol, Array + true + else + false + end + end + + join_dependency = ActiveRecord::Associations::JoinDependency.new(other.klass, association_joins, rest_joins) + scope.joins! join_dependency.join_constraints([], Arel::Nodes::InnerJoin).map(&:joins) + end + def eval_scope(klass, scope, owner) klass.unscoped.instance_exec(owner, &scope) end 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 aff0dabee75ba..8b096e3b6a867 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -1215,4 +1215,13 @@ def test_has_many_through_do_not_cache_association_reader_if_the_though_method_h ensure TenantMembership.current_member = nil end + + def test_has_many_through_with_joins_in_scope + developer = Developer.create! name: 'Jamis' + post = Post.first + author = Author.create! name: 'John' + Comment.create! developer: developer, post: post, body: '', author: author + + assert_includes post.reload.jamises_developer_comments_authors, author + end end diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb index bf3079a1dffd3..a0e04a767a8a1 100644 --- a/activerecord/test/models/post.rb +++ b/activerecord/test/models/post.rb @@ -60,6 +60,9 @@ def the_association end end + has_many :jamises_developer_comments, -> { joins(:developer).merge(Developer.jamises) }, class_name: 'Comment' + has_many :jamises_developer_comments_authors, through: :jamises_developer_comments, source: :author, source_type: 'Author' + has_many :comments_with_extend, extend: NamedExtension, class_name: "Comment", foreign_key: "post_id" do def greeting "hello"