Can't auto eager load has_many through association with a scope that uses includes
jturkel opened this issue · comments
Consider the following models:
class Blog < ActiveRecord::Base
has_many :posts
has_many :authors, -> { includes(:address).where('addresses.city IS NOT NULL').references(:address) }, through: :posts
end
class Post < ActiveRecord::Base
belongs_to :blog
belongs_to :author
end
class Author < ActiveRecord::Base
has_many :posts
has_one :address
end
class Address < ActiveRecord::Base
belongs_to :author
end
Attempting to auto eager load (or regular eager load) Blog#authors
results in the following error:
ActiveRecord::ConfigurationError: Association named 'address' was not found on Post; perhaps you misspelled it?
Underlying Rails bug is fixed by rails/rails#12725. Workaround in Golidloader is to set auto_include = false
.
The context in the scope for has_many through associations incorrectly points to the source association rather than the target association when eager loading. This breaks eager loading of most has_many through associations that use scopes.
I've confirmed this problem still exists in Rails master. Reproducible test case can be found here: https://gist.github.com/jturkel/fc4cca3949724da4b902
Looks like there's a new Rails PR up to fix this issue: rails/rails#23100
is this still an issue?
@NullVoxPopuli - Yes, it's still an issue. The Rails bug still seems to be present in Rails 5.1.2. See this gist.
Encountered the same problem (ruby 2.4.1, Rails 5.1.4). Only I do not have any includes
in the scope
class Blog < ApplicationRecord
has_many :posts
has_many :authors, -> { where name: 'James' }, through: :posts
end
class Post < ApplicationRecord
belongs_to :author
belongs_to :blog
end
class Author < ApplicationRecord
has_many :posts
end
Of course, I started with something more complex in the scope, only when debugging, I found, that it didn't really matter what I put in there as long as it shows up in the where clause...
So, when I query Blog.includes(:authors)
, it tries the following lines of SQL:
SELECT `blogs`.* FROM `blogs`
SELECT `posts`.* FROM `posts` WHERE `authors`.`name` = 'James'
AND `posts`.`bog_id` IN (1, 2)
Where the clause authors.name = 'James'
does not make sense for mysql, since it made no queries to the table authors
.
Also, if I remove the scope from has_many :authors
, the query above runs the same two line (except for the where clause, of course), and the authors
table is not even queried until I really want something with them (like Blog.includes(:authors).map { |blog| blog.authors.map &:name }
). Then it runs one extra query:
SELECT `authors`.* FROM `authors` WHERE `authors`.`id` IN (1, 2)
Lastly, if I put the scope on the join association
class Blog < ApplicationRecord
has_many :posts
has_many :posts_of_james, -> { joins(:author).where(authors: {name: 'James'}) }, class_name: 'Post'
has_many :authors, through: :posts_of_james
end
it runs the inner join query missing from the first approach.
SELECT `posts`.* FROM `posts` INNER JOIN `authors`
ON `authors`.`id` = `posts`.`author_id`
WHERE (`authors`.`name` = 'James') AND `posts`.`id` IN (1, 2)
SELECT `authors`.* FROM `authors` WHERE `authors`.`id` IN (1, 2)
This has been fixed in Rails 5.2. See this gist for a reproducible test case.