Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Eager Load Depending on Type of Association in Ruby on Rails

I have a polymorphic association (belongs_to :resource, polymorphic: true) where resource can be a variety of different models. To simplify the question assume it can be either a Order or a Customer.

If it is a Order I'd like to preload the order, and preload the Address. If it is a customer I'd like to preload the Customer and preload the Location.

The code using these associations does something like:

<%- @issues.each do |issue| -%>
<%- case issue.resource -%>
<%- when Customer -%>
<%= issue.resource.name %> <%= issue.resource.location.name %>
<%- when Order -%>
<%= issue.resource.number %> <%= issue.resource.address.details %>
<%- end -%>

Currently my preload uses:

@issues.preload(:resource)

However I still see n-plus-one issues for loading the conditional associations:

SELECT "addresses".* WHERE "addresses"."order_id" = ...
SELECT "locations".* WHERE "locations"."customer_id" = ...
...

What's a good way to fix this? Is it possible to manually preload an association?

like image 756
Stussa Avatar asked Mar 13 '17 21:03

Stussa


People also ask

How eager loading works in Rails?

Eager loading lets you preload the associated data (authors) for all the posts from the database, improves the overall performance by reducing the number of queries, and provides you with the data that you want to display in your views, but the only catch here is which one to use. Gotcha!

What is polymorphic association in Ruby on Rails?

In Ruby on Rails, a polymorphic association is an Active Record association that can connect a model to multiple other models. For example, we can use a single association to connect the Review model with the Event and Restaurant models, allowing us to connect a review with either an event or a restaurant.

How is polymorphic association set up in Rails?

The basic structure of a polymorphic association (PA)sets up 2 columns in the comment table. (This is different from a typical one-to-many association, where we'd only need one column that references the id's of the model it belongs to). For a PA, the first column we need to create is for the selected model.

What is Eager_load?

Eager_load loads all columns in single query But Includes loads the data as per needed.. It works as preload in default case and works as eager_load in case of additional query in associated table.


2 Answers

You can do that with the help of ActiveRecord::Associations::Preloader class. Here is the code:

@issues = Issue.all # Or whatever query
ActiveRecord::Associations::Preloader.new.preload(@issues.select { |i| i.resource_type == "Order" }, { resource: :address })
ActiveRecord::Associations::Preloader.new.preload(@issues.select { |i| i.resource_type == "Customer" }, { resource: :location })

You can use different approach when filtering the collection. For example, in my project I am using group_by

groups = sale_items.group_by(&:item_type)
groups.each do |type, items|
  conditions = case type
  when "Product" then :item
  when "Service" then { item: { service: [:group] } }
end

ActiveRecord::Associations::Preloader.new.preload(items, conditions)

You can easily wrap this code in some helper class and use it in different parts of your app.

like image 188
Michał Młoźniak Avatar answered Nov 15 '22 15:11

Michał Młoźniak


This is now working in Rails v6.0.0.rc1: https://github.com/rails/rails/pull/32655

You can do .includes(resource: [:address, :location])

like image 36
thisismydesign Avatar answered Nov 15 '22 17:11

thisismydesign