Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails has_many with `through` option "loses" joins?

I have the following example model structure:

class Category < ActiveRecord::Base
  has_many :posts

  scope :active, -> { where(active: true) }
end

class User < ActiveRecord::Base
  has_many :posts
  has_many :visible_posts, -> { joins(:category).merge(Category.active) }, class: Post
  has_many :visible_posts_comments, through: :visible_posts, source: :comments

  has_many :comments
end

class Post < ActiveRecord::Base
  belongs_to :category
  belongs_to :user
  has_many :comments
end

class Comment < ActiveRecord::Base
  belongs_to :post
  belongs_to :user
end

Now a User.first.visible_posts_comments raises the following error:

ActiveRecord::StatementInvalid: PG::UndefinedTable: ERROR:  missing FROM-clause entry for table "categories"
LINE 1: ..." = "posts"."id" WHERE "posts"."user_id" = $1 AND "categorie...

Which is because the SQL which is executed by this association is the following:

2.1.2 :009 > u.visible_posts_comments.to_sql
 => "SELECT \"comments\".* FROM \"comments\" INNER JOIN \"posts\" ON \"comments\".\"post_id\" = \"posts\".\"id\" WHERE \"posts\".\"user_id\" = $1 AND \"categories\".\"active\" = 't'"

While visible_posts works properly by adding the INNER JOIN on categories,

2.1.2 :010 > u.visible_posts.to_sql
 => "SELECT \"posts\".* FROM \"posts\" INNER JOIN \"categories\" ON \"categories\".\"id\" = \"posts\".\"category_id\" WHERE \"posts\".\"user_id\" = $1 AND \"categories\".\"active\" = 't'"

why does visible_posts_comments seem to "lose" the joins(:category) statement but keeps the merge(Category.active)? I see no reason to drop the joins of the through-association on purpose. Is this a bug or a feature?

I am using activerecord-4.1.8.

Could be related to this: https://github.com/rails/rails/issues/17904

like image 518
Steve Beer Avatar asked Dec 03 '14 15:12

Steve Beer


1 Answers

I've created a rails project the same as yours, found the same issue. Two points on this issue:

1. has_many :through will delete "joins" from the through relations, source code:

#lib/active_record/associations/through_association.rb  line 14
    def target_scope
      scope = super
      chain.drop(1).each do |reflection|
        relation = reflection.klass.all
        relation.merge!(reflection.scope) if reflection.scope

        scope.merge!(
          relation.except(:select, :create_with, :includes, :preload, :joins, :eager_load)
        )

      end
      scope
    end

I think the reason they did like this is a consideration of record creating operations. ex. Maybe u.visible_posts_comments.create(...) will make ActiveRecord confused

2. A walkaround way for you:

class Category < ActiveRecord::Base
  has_many :posts
end

class User < ActiveRecord::Base
  has_many :posts
  has_many :visible_posts, -> { merge(Post.active) }, class: Post
  has_many :visible_posts_comments, -> { joins(:post).merge(Post.active) }, class: Comment

  has_many :comments
end

class Post < ActiveRecord::Base
  belongs_to :category
  belongs_to :user
  has_many :comments

  scope :active, -> { joins(:category).merge(Category.active) }
end

class Comment < ActiveRecord::Base
  belongs_to :post
  belongs_to :user
end
like image 108
brookz Avatar answered Sep 28 '22 04:09

brookz