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?

+3


source to share


1 answer


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.

+2


source







All Articles