I've written about Ransack before, because it's a handy, little gem for searching quickly through both models and their relationships. But you may be surprised to know it's deceptively easy to create N+1 queries using Ransack! The good news is, it's just as easy to fix.
To review basic Ransack use, let's say you have a model Service
, which has properties for name
and description
. If you wanted to search both of those properties, you'd set up your view with something like this:
<%= search_form_for @q do |f| %>
<%= f.label :name_cont %>
<%= f.search_field :name_description_cont %>
<% end %>
And your controller would be:
def index
@q = Service.ransack(params[:q])
@services = @q.result(distinct: true)
end
Pretty basic stuff, right? But what if you have another model associated with Services? For example, let's say you have a Provider
model with name
and address
properties, and each Provider
has_many Services
.
Now, you want to search the name
and description
properties for Service
, but you also want to include the Provider
s name
property as well. How do you do that?
Ransack provides an easy approach. You can change your view to:
<%= search_form_for @q do |f| %>
<%= f.label :name_cont %>
<%= f.search_field :name_description_provider_name_cont %>
<% end %>
Things are working smoothly. But hold on, let's look at the SQL query that runs when we try to search for something:
Yikes! That's not what we want at all!
Those familiar with rails will know the dreaded N+1 Problem, but will also likely know we can usually solve it with eager loading and using includes(:model_name)
. But where do we put it? Easy, in the Controller of course!
def index
@q = Service.includes(:provider).ransack(params[:q])
@services = @q.result(distinct: true)
end
Running our search again:
(Please note that in my examples, I also have Sub Categories loading for another part of my view)
This isn't a difficult problem to solve, but many may not be aware that using Ransack with associated models can end up creating this N+1 problems.
Good luck, and happy coding!
Top comments (6)
Super helpful little post
Hi @husteadrobert, small question what if you search with multiple models ? each model has diff relationships
Any reasons not to use relations and joins?
Sorry for the late reply!
I'm not sure I understand your question. In the example,
Service
andProvider
are related, and I suppose you could do something like,perhaps? But I have not tested it. Moreover, this would bypass any resource authorization libraries (like Cancan)
Awesome reminder! Thanks!
doesn't using includes eagerload or preload the association: wouldn't it be more efficient to simply use a joins method if in fact we are only seeking Service records?