使用includes匹配嵌套模型关联属性

Chr*_*erg 5 activerecord associations arel active-relation ruby-on-rails-3

假设我有以下型号:

class Post < ActiveRecord::Base
  has_many :authors

class Author < ActiveRecord::Base
  belongs_to :post
Run Code Online (Sandbox Code Playgroud)

假设Author模型有一个属性,name.

我想通过该作者的名字搜索给定作者"alice"的所有帖子.假设有另一位作者"bob"与alice共同撰写了一篇文章.

如果我使用includes和搜索第一个结果where:

post = Post.includes(:authors).where("authors.name" => "alice").first
Run Code Online (Sandbox Code Playgroud)

您会看到该帖子现在只有一位作者,即使实际上还有更多:

post.authors #=> [#<Author id: 1, name: "alice", ...>]
post.reload
post.authors #=> [#<Author id: 1, name: "alice", ...>, #<Author id: 2, name: "bob", ...>]
Run Code Online (Sandbox Code Playgroud)

这个问题似乎是的组合includeswhere,其正确地限制了范围,以所希望的文章,但在同一时间隐藏不同的是匹配所述一个的所有关联.

我想最终得到一个ActiveRecord::Relation链接,所以上面的重载解决方案并不是真的令人满意.替换includesjoins解决此问题,但不急于加载关联:

Post.joins(:authors).where("authors.name" => "alice").first.authors
#=> [#<Author id: 1, name: "alice", ...>, #<Author id: 2, name: "bob", ...>]
Post.joins(:authors).where("authors.name" => "alice").first.authors.loaded?
#=> false
Run Code Online (Sandbox Code Playgroud)

有什么建议?在此先感谢,我一直在为这个问题敲打我的脑袋.

Chr*_*erg 1

很长一段时间后回到这个问题,我意识到有更好的方法来做到这一点。关键是不是进行一次连接,而是进行两次连接,一个includes使用表别名,一个使用 Arel:

posts   = Post.arel_table
authors = Author.arel_table.alias("matching_authors")
join    = posts.join(authors, Arel::Nodes::InnerJoin).
                on(authors[:post_id].eq(posts[:id])).join_sources

post = Post.includes(:authors).joins(join).
            where(matching_authors: { name: "Alice" }).first
Run Code Online (Sandbox Code Playgroud)

这个查询的 SQL 相当长,因为它有includes,但关键点是它有两个连接,而不是一个,一个(来自includes)使用LEFT OUTER JOIN别名上的a posts_authors,另一个(来自 Arel join)使用INNER JOIN别名上的an matching_authors。仅WHERE适用于后一个别名,因此返回结果中关联的结果不受此条件限制。