Rails 4 keep order of union object when using include in has_many via association
Using Rails 4.1.6 and Ruby 2.1.2 and define the following three objects:
class FilmCollection
has_many :film_collection_films, -> { order(:position) }
has_many :films, through: :film_collection_films
end
class FilmCollectionFilm
belongs_to :film_collection_film
belongs_to :film
end
class Film
has_many :film_collection_films
end
FilmCollections are collections of films, and their items are represented through the FilmCollectionFilm union object. FilmCollectionFilm has a column for position, so that a given member of the collection can be reordered (think like a movie queue) and so we need a join object, not has_and_belongs_to_many.
My problem occurs when I try to download movies from multiple collections at once.
The call FilmCollection.first.films
will give me a request that looks like this:
SELECT `film_collections`.* FROM `film_collections`
ORDER BY `film_collections`.`id` ASC LIMIT 1
SELECT `films`.* FROM `films`
INNER JOIN `film_collection_films` ON `films`.`id` = `film_collection_films`.`film_id`
WHERE `film_collection_films`.`extensional_film_collection_id` = 1
ORDER BY `film_collection_films`.`position` ASC
Which orders the films correctly based on the position in the pooling object.
But instead, calling FilmCollection.includes (: films) .first.films will give me the following query:
SELECT `film_collections`.* FROM `film_collections`
ORDER BY `film_collections`.`id` ASC LIMIT 1
SELECT `film_collection_films`.* FROM `film_collection_films`
WHERE `film_collection_films`.`film_collection_id` IN (1)
ORDER BY `film_collection_films`.`position` ASC
SELECT `films`.* FROM `films` WHERE `films`.`id` IN (1, 16, 53, 185)
Which collects the correct movies, but ignores the order in the second part of the query. How can I keep the ordering of the corresponding connection object on active load with .includes()
, but still doesn't have an n + 1 query?
source to share
It turns out that you can work around this by explicitly including the join object as well as its associated relationship. The line below will select everything correctly and will preserve any conventions / ordering present on the join object.
FilmCollection.includes(film_collection_films: :film)
If you are simple FilmCollection.includes(:films)
then it will select the FilmCollectionFilms anyway and then just throw them. Thus, the above code will do about the same amount of work, but will build the query correctly.
source to share