Ad Hoc Associations
Docs: eager_load_many, eager_load_one
On rare occasions you may need to eager load an association that doesn't actually exist in your models. Maybe it's too convoluted to represent with ActiveRecord. Or maybe it's just deeply nested and you don't want to waste time/memory loading all the intermediate records.
eager_load_many
The following example uses eager_load_many
to load a non-existent, has-many association on Product
called customers
. Each product will have a customers
attribute that contains the customers who bought the product.
OccamsRecord.
query(Product.all).
eager_load_many(:customers, {:id => :product_id}, "
SELECT DISTINCT product_id, customers.*
FROM line_items
INNER JOIN orders ON line_items.order_id = orders.id
INNER JOIN customers on orders.customer_id = customers.id
WHERE
line_items.product_id IN (:ids)
AND customers.created_at >= :date
", binds: {
date: params[:date]
})
That's a lot, so we'll break it down. The method call really just looks like this:
eager_load_many(:customers, {:id => :product_id}, "SOME SQL", binds: {date: some_date})
The first argument, :customers
, simply gives this made-up association a name. We'll call product.customers
to get a product's customers.
The second argument, {:id => :product_id}
defines the parent-child mapping. In this case it says, "The parent product records have an id
field, and it will match the product_id
field in the child customers."
The third argument is the SQL that loads customers. Notice the line_items.product_id IN (:ids)
section. That's ensuring we're only loading customers that are related to the products we've loaded. OccamsRecord will provide those ids for us - don't worry. (And it's only called :ids
because we defined the parent mapping as :id
. If the parent mapping was instead :code
, we'd put :codes
in the SQL.)
The forth argument is optional. It can be a Hash or Array of any other query parameters you need.
eager_load_one
eager_load_one
works exactly the same but for one-to-one relationships.
Nesting ad hoc associations
Like other eager loads, you can nest ad hoc ones. Here's an eager_load_many
with an eager_load_one
nested inside:
OccamsRecord.
query(Product.all).
eager_load_many(:customers, {:id => :product_id}, "SELECT...") {
eager_load_one(:something, {:id => :customer_id}, "SELECT...")
}
Here's an eager_load_many
with a regular eager_load
nested!
OccamsRecord.
query(Product.all).
eager_load_many(:customers, {:id => :product_id}, "SELECT...", model: Customer) {
eager_load(:profile)
}
Notice that we added model: Customer
to eager_load_many
's arguments. That annotates the ad hoc association with the model, allowing us to use the regular eager_load
on Customer
associations like :profile
.