After I upgraded rails from 5.1 to 5.2 I started getting the following error:
NoMethodError: undefined method `expr' for nil:NilClass
from /gems_path/activerecord-5.2.0/lib/active_record/associations/join_dependency/join_association.rb:47:in `block in join_constraints'
from /gems_path/activerecord-5.2.0/lib/active_record/associations/join_dependency/join_association.rb:33:in `reverse_each'
from /gems_path/activerecord-5.2.0/lib/active_record/associations/join_dependency/join_association.rb:33:in `join_constraints'
from /gems_path/activerecord-5.2.0/lib/active_record/associations/join_dependency.rb:167:in `make_constraints'
from /gems_path/activerecord-5.2.0/lib/active_record/associations/join_dependency.rb:177:in `make_join_constraints'
from /gems_path/activerecord-5.2.0/lib/active_record/associations/join_dependency.rb:104:in `block in join_constraints'
from /gems_path/activerecord-5.2.0/lib/active_record/associations/join_dependency.rb:103:in `each'
from /gems_path/activerecord-5.2.0/lib/active_record/associations/join_dependency.rb:103:in `flat_map'
from /gems_path/activerecord-5.2.0/lib/active_record/associations/join_dependency.rb:103:in `join_constraints'
from /gems_path/activerecord-5.2.0/lib/active_record/relation/query_methods.rb:1026:in `build_join_query'
from /gems_path/activerecord-5.2.0/lib/active_record/relation/query_methods.rb:1008:in `build_joins'
from /gems_path/activerecord-5.2.0/lib/active_record/relation/query_methods.rb:928:in `build_arel'
from /gems_path/activerecord-5.2.0/lib/active_record/relation/query_methods.rb:903:in `arel'
from /gems_path/activerecord-5.2.0/lib/active_record/relation.rb:554:in `block in exec_queries'
from /gems_path/activerecord-5.2.0/lib/active_record/relation.rb:578:in `skip_query_cache_if_necessary'
from /gems_path/activerecord-5.2.0/lib/active_record/relation.rb:542:in `exec_queries'
from /gems_path/activerecord-5.2.0/lib/active_record/relation.rb:414:in `load'
from /gems_path/activerecord-5.2.0/lib/active_record/relation.rb:200:in `records'
from /gems_path/activerecord-5.2.0/lib/active_record/relation/delegation.rb:41:in `[]'
The code which causes the error looks the following way:
class Post
has_many :comments
has_one :last_comment, -> {
joins("LEFT JOIN posts on posts.id = comments.post_id")
.where("
comments.id = (
SELECT MAX(comments.id) FROM comments
WHERE comments.post_id = posts.id
)"
)
}, class_name: "Comment"
scope :with_last_comment, -> { joins(:last_comment) }
end
I created this gist which contains full code which helps to reproduce the bug. Download issue_with_joins_in_scopes_in_rails_5_3.rb
to your PC and run it with
ruby issue_with_joins_in_scopes_in_rails_5_3.rb
You can look at this Github issue for more details
What is the difference between joins in Rails 5.2 and 5.1 which causes the code Post.with_last_comment
to fail with error in Rails 5.2?
How can I change last_comment
association and with_last_comment
scope in the Post model so it will work in Rails 5.2?
Posting partial solution on how to preload last comment for a collection of posts
First you will need to have the following last_comment
association:
class Post < ApplicationRecord
has_many :comments, dependent: :destroy
has_one :last_comment, -> { order(id: :desc) }, class_name: "Comment"
end
It works with preload
and includes
in Rails 5.2 and generates the following SQL queries:
Post.includes(:last_comment)
Post Load (0.2ms) SELECT "posts".* FROM "posts" LIMIT ? [["LIMIT", 11]]
Comment Load (0.2ms) SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = ? ORDER BY "comments"."id" DESC [["post_id", 1]]
The problem with this solution is that when I use joins
it ignores association scope and generates the following SQL query:
Post.joins(:last_comment)
Post Load (0.2ms) SELECT "posts".* FROM "posts" INNER JOIN "comments" ON "comments"."post_id" = "posts"."id" LIMIT ? [["LIMIT", 11]]
I was able to solve it changing the original last_comment
association the following way:
class Post < ApplicationRecord
has_many :comments, dependent: :destroy
has_one :last_comment, -> {
joins(:post) # <--- Note this change
.where("
comments.id = (
SELECT MAX(comments.id) FROM comments
WHERE comments.post_id = posts.id
)"
)
}, class_name: "Comment"
scope :with_last_comment, -> { joins(:last_comment) }
end
And now Post.with_last_comment
generates the following SQL:
Post Load (0.3ms) SELECT "posts".* FROM "posts" INNER JOIN "comments" ON "comments"."post_id" = "posts"."id" INNER JOIN "posts" "posts_comments" ON "posts_comments"."id" = "comments"."post_id" AND (
comments.id = (
SELECT MAX(comments.id) FROM comments
WHERE comments.post_id = posts.id
)) LIMIT ? [["LIMIT", 11]]
The question on how joins in Rails 5.2 are different from Rails 5.1 is still open
来源:https://stackoverflow.com/questions/57074698/how-are-joins-in-scopes-in-rails-5-2-different-from-rails-5-1