Smart way of not getting N+1 while querying has_many relationship in Rails?

40 Views Asked by At

I have a Movie model and a MovieStream model. The relation is Movie has_many MediaType (and MediaType belongs_to Movie).

MediaType has two attribues "url_broken" (true/false) and "dvd" (true/false).

In my view I have:

Movie.all.includes(:movie_item).each do |movie|
  render partial: 'movie_item'
end

and in movie_item-partial I have (simplified):

<h1><%= movie.title %></h1>

<% if movie.media_types.where(:url_broken => false).blank? %>
  No media types exist
<% else %>
<h2>Streams</h2>
<% streams = movie.media_types.where(:dvd => false).each do |media_type| %>
  * <%= media_type.name %>
<% end %>

<h2>Dvd's</h2>
<% dvds = movie.media_types.where(:dvd => true).each do |media_type| %>
  * <%= media_type.name %>
<% end %>

This (obviously) causes N+1 queries but I cannot see any way around it. Is it possible, in some way, to query the view-query in a way that I can get the streams and dvds at the same time?

Like:

Movie.all.join_stream_something.each do |movie, streams, dvds|
render partial: 'movie_item', :locals => { :movie => movie, :streams => streams, :dvds => dvds}
end

to not have N+1? end

2

There are 2 best solutions below

1
tywhang On

Looks like you just need to use an .includes

https://apidock.com/rails/ActiveRecord/QueryMethods/includes

Movie.includes(:media_type).all.each do |movie|
  render partial: 'movie_item', :locals => { :movie => movie }
end
1
Mshka On

you can define a scoped has_many(s) in the movie class

class Movie < ApplicationRecord
  has_many :valid_url_media_types, -> { where(url_broken: false) }, class_name: "MediaType"
  has_many :not_dvd_media_types, -> { where(dvd: false) }, class_name: "MediaType"
  has_many :dvd_media_types, -> { where(dvd: true) }, class_name: "MediaType"
end

then in your view

Movie.all.includes(:valid_url_media_types, :not_dvd_media_types, :dvd_media_types).each do |movie|
  render partial: 'movie_item', movie: movie
end

your partial:

<h1><%= movie.title %></h1>

<% if movie.valid_url_media_types.blank? %>
  No media types exist
<% else %>
<h2>Streams</h2>
<% streams = movie.not_dvd_media_types.each do |media_type| %>
  * <%= media_type.name %>
<% end %>

<h2>Dvd's</h2>
<% dvds = movie.dvd_media_types.each do |media_type| %>
  * <%= media_type.name %>
<% end %>