On Wednesday, 13 January 2016 16:59:34 UTC-5, Flemming Thesbjerg wrote:
When running the code from the gist it will fail to load the join table
and therefore fail.
Any help would be greatly appreciated.
It would be helpful to see the exact SQL generated when the tests in
that
Gist run. People can run the example, but it’s an extra step.
The query fails because the post_artifacts
table isn’t joined. This is
an
expected behavior of includes
; it chooses between a preload (which
requires one additional query but fewer joins) and an eager load (which
widens the query with a join). To do this, it relies on checking to see
if
the included tables are referenced in the SQL. Putting conditions on the
join table (post_artifacts, here) in a through association (a_comments,
here) without hinting will cause this behavior.
There are at least two ways to work around this problem:
- first alternative: explicitly build an association of PostArtifacts
that
have the condition applied. Change the Post model to:
class Post < ActiveRecord::Base
has_many :post_artifacts
has_many :comments, through: :post_artifacts, source: :artifact,
source_type: ‘Comment’
has_many :a_post_artifacts, -> { a }, class_name: ‘PostArtifact’
has_many :a_comments, through: :a_post_artifacts, source: :artifact,
source_type: ‘Comment’
end
This moves the condition to a place where ActiveRecord understands the
post_artifacts
table will be referenced when preloading a_comments
.
The
resulting SQL looks like:
SELECT “posts”.* FROM “posts”
SELECT “post_artifacts”.* FROM “post_artifacts” WHERE
“post_artifacts”.“rule” = ? AND “post_artifacts”.“artifact_type” = ? AND
“post_artifacts”.“post_id” = 1 [[“rule”, 0], [“artifact_type”,
“Comment”]]
SELECT “comments”.* FROM “comments” WHERE “comments”.“id” IN (1, 2, 3,
4,
5, 6, 7, 8, 9, 10)
- second alternative: explicitly specify
references
at the callsite.
Leave the associations as-is from the Gist and change the second assert
in
the test to:
assert_equal 10,
Post.includes(:a_comments).references(:post_artifacts).flat_map(&:a_comments).count
This uses references
to inform ActiveRecord that loading the requested
Posts also requires post_artifacts.
The generated SQL looks different than the previous case, as
references
forces eager-load instead of preload:
SELECT “posts”.“id” AS t0_r0, “posts”.“title” AS t0_r1, “comments”.“id”
AS
t1_r0, “comments”.“content” AS t1_r1 FROM “posts” LEFT OUTER JOIN
“post_artifacts” ON “post_artifacts”.“post_id” = “posts”.“id” AND
“post_artifacts”.“artifact_type” = ? LEFT OUTER JOIN “comments” ON
“comments”.“id” = “post_artifacts”.“artifact_id” AND
“post_artifacts”.“rule” = ? [[“artifact_type”, “Comment”], [“rule”, 0]]
One thing that doesn’t work yet: specifying references
in the scope
passed to has_many
.
–Matt J.