elixirdrops / kerosene

Pagination for Ecto and Pheonix.

Home Page:https://github.com/elixirdrops/kerosene

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Is it possible to paginate an association?

joshchernoff opened this issue · comments

Tried an idea I'd hoped would work but knew would not. Still I think it conveys what I'm trying to do.

 def get_tag!(tag, params \\ %{}) do
    {pics_query, k} = from(p in Pic, order_by: p.inserted_at) |> Repo.paginate(params, lazy: true)

    tag =
      from(t in Tag, where: t.name == ^tag, preload: [pics: ^pics_query])
      |> Repo.one!()

    {tag, k}
  end

The ideas being just as the title asks.

Thoughts?

After reading some of the source code I realized that if I forgo the repo.all in

kerosene/lib/kerosene.ex

Lines 46 to 52 in ca90a54

defp get_items(repo, query, nil, _), do: repo.all(query)
defp get_items(repo, query, limit, offset) do
query
|> limit(^limit)
|> offset(^offset)
|> repo.all
end

I can return the query which I could then just pass in my preload to achieve the effect I'm looking for.

Whats the thought about making a new function that mimics paginate but does not fetch the records or adding an option to paginate like lazy where is true it will return a query vs records?

There is one thing that should be considered as noted in ectos docs.

Note: keep in mind operations like limit and offset in the preload query will affect the whole result set and not each association. For example, the query below:

comments_query = from c in Comment, order_by: c.popularity, limit: 5
Repo.all from p in Post, preload: [comments: ^comments_query]

won’t bring the top of comments per post. Rather, it will only bring the 5 top comments across all posts.

The only obvious issue I see with this approach is that it requires some work to get_total_count to address the fact that the pics in question are limited by their association to tag and thus get_total_count will return the wrong total.

Here is my working example for using kerosene on a preload. Note this is a wip and is using my fork.
PolymorphicProductions/kerosene

def get_tag!(tag, params \\ %{}) do
    down_tag = String.downcase(tag)

    total_count =
      from(t in "tags",
        join: pt in "pic_tags",
        on: pt.tag_id == t.id,
        where: t.name == ^down_tag,
        select: count()
      )
      |> Repo.one()

    {pics_query, k} =
      from(p in Pic, order_by: p.inserted_at)
      |> Repo.paginate(params, total_count: total_count, lazy: true)

    tag =
      from(t in Tag, where: t.name == ^down_tag, preload: [pics: ^pics_query])
      |> Repo.one!()

    {tag, k}
  end

I ended up having to manage my own total count since I didn't want the preload query to be ran to figure that out. Also note the use of the lazy option.