Rails has_many с опцией `through` "проигрывает"?

У меня есть следующая модельная модель:

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

Теперь a User.first.visible_posts_comments вызывает следующую ошибку:

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

Это потому, что SQL, который выполняется этой ассоциацией, выглядит следующим образом:

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'"

Пока visible_posts работает правильно, добавив 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'"

почему visible_posts_comments кажется "потерять" оператор joins(:category), но сохраняет merge(Category.active)? Я не вижу причин для отказа в объединении through -сообщения. Это ошибка или функция?

Я использую activerecord-4.1.8.

Может быть связано с этим: https://github.com/rails/rails/issues/17904

Ответ 1

Я создал проект рельсов так же, как и ваш, нашел ту же проблему. Два вопроса по этому вопросу:

1. has_many: через будет удалять "соединения" из сквозных отношений, исходный код:

#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

Я думаю, что причина, по которой они это сделали, - это рассмотрение операций создания записей. ех. Возможно, u.visible_posts_comments.create(...) заставит ActiveRecord запутать

2. Дорожный путь для вас:

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