Pagination with Phoenix and Ecto

Tagged ecto, elixir, pagination, phoenix  Languages elixir

Tested with Ecto 2.2 and Phoenix 1.3.

This module allows you to perform pagination with Phoenix and Ecto:

defmodule Pagination do
  import Ecto.Query
  alias Snippets.Repo
  #
  # ## Example
  #
  #    Snippets.Snippet
  #    |> order_by(desc: :inserted_at)
  #    |> Pagination.page(0, per_page: 10)
  #
  def page(query, page, per_page: per_page) when is_nil(page) do
    page(query, 0, per_page: per_page)
  end

  def page(query, page, per_page: per_page) when is_binary(page) do
    page = String.to_integer(page)
    page(query, page, per_page: per_page)
  end

  def page(query, page, per_page: per_page) do
    count = per_page + 1
    result = query
             |> limit(^count)
             |> offset(^(page*per_page))
             |> Repo.all
    has_next = (length(result) == count)
    has_prev = page > 0
    total_count = Repo.one(from t in subquery(query), select: count("*"))
    page = %{
      has_next: has_next,
      has_prev: has_prev,
      prev_page: page - 1,
      next_page: page + 1,
      page: page,
      first: page*per_page+1,
      last: Enum.min([page+1*per_page, total_count]),
      count: total_count,
      list: Enum.slice(result, 0, count-1)
    }
  end
end

Usage:

Snippets.Snippet
|> order_by(desc: :inserted_at)
|> Pagination.page(0, per_page: 10)

View helper

defmodule PaginationHelpers do
  import Phoenix.HTML
  import Phoenix.HTML.Form
  import Phoenix.HTML.Link
  import Phoenix.HTML.Tag

  def pagination_text(list) do
    ~e"""
    Displaying <%= list.first %>-<%= list.last %> of <%= list.count %>
    """
  end

  def pagination_links(conn, list, route) do
    content_tag :div, class: "pagination" do
      children = []
      if list.has_prev do
        children = children ++ link "Previous", to: route.(conn, :index, page: list.prev_page), class: "btn btn-secondary col-md-1"
      end
      if list.has_next do
        children = children ++ link "Next", to: route.(conn, :index, page: list.next_page), class: "btn btn-secondary col-md-1"
      end
      children
    end
  end
end

Usage:

<%= pagination_text(@snippets) %>
<%= pagination_links(@conn, @snippets, &snippet_path/3) %>