Adding a Limit Option to Ecto's Repo.preload in Elixir

209 views Asked by At

I have a function in Elixir using Ecto that preloads associations for a given entity. The current implementation looks like this:

@spec get(integer() | String.t(), list()) :: t() | nil
def get(id, preloads \\ []) do
  Shop
  |> Repo.get(id)
  |> Repo.preload(preloads)
end

i use it like this:

      Shop.get(id, [
        :coustomers,
        :products
      ])

and is it possible do make it like this ?:

  Shop.get(id, [
    :coustomers,
    {:limit,:products,3}
  ])
2

There are 2 answers

5
zwippie On

It is possible to limit the number of preloaded records, but it takes more than a one liner and is maybe not as flexible as you would hope or expect:

products_query = from p in Product, limit: 3, order_by: p.inserted_at

Shop
|> Repo.get(id)
|> Repo.preload([products: products_query]) 

Maybe you can create a function that takes a Module and a number (for the limit) and returns something identical to the products_query above, like:

def preload_query(module, limit) do
  from a in module, limit: ^limit
end

And use this as an argument for Shop.get/2:

Shop.get(id, [:customers, products: preload_query(Product, 3)])
0
Peaceful James On

I would do something like this:

defmodule Shops do
  def get_shop(nil), do: nil

  def get_shop(shop_id) do
    limited_products =
      Product
      |> where(shop_id: ^shop_id)
      |> limit(3)

    Shop
    |> from(as: :shop)
    |> join(:inner, [shop: shop], product in subquery(limited_products), on: true, as: :products)
    |> select([shop: shop, products: products], %{shop | products: products})
    |> Repo.one()
  end
end

I have not tested this. It is just for illumination.