Ecto is powerful enough that we just don't need to pull in external dependencies to do pagination in Phoenix.

This episode covers making a Pagination module, and using from controllers and contexts. We'll build it for our StatWatch project, but the same strategy works for any Phoenix app. We'll paginate two different views, first a simple index view and then a more complex one where the paginated part is pre-loaded into another schema.

Core.ex

Here's what our Core context module looks like at the end:

defmodule StatWatch.Core do @moduledoc """ The Core context. This contains logic for schemas imported from non-web version of StatWatch """ import Ecto.Query, warn: false alias StatWatch.{Repo, Pagination, Profile, Stat} alias StatWatch.Accounts.User @days_per_page 30 @profiles_per_page 5 def list_profiles do Repo.all(Profile) end def list_profiles(a, page \\ 1, per_page \\ @profiles_per_page) def list_profiles(:paged, page, per_page) do Profile |> order_by(desc: :name) |> Pagination.page(page, per_page: per_page) end def list_profiles(user = %User{}, page, per_page) do Profile |> where(user_id: ^user.id) |> order_by(desc: :name) |> Pagination.page(page, per_page: per_page) end def get_profile!(id) do stat_query = from(s in Stat, order_by: [desc: s.inserted_at]) Repo.get!(Profile, id) |> Repo.preload([:user, stats: stat_query]) end def get_profile_by_name!(name) do stat_query = from(s in Stat, order_by: [desc: s.inserted_at]) Repo.get_by!(Profile, %{name: name}) |> Repo.preload([:user, stats: stat_query]) end def get_profile_by_name!(name, page, per_page \\ @days_per_page) do profile = Repo.get_by!(Profile, %{name: name}) |> Repo.preload([:user]) stats = page_of_stats(profile.id, page, per_page) Map.put(profile, :paginated_stats, stats) end def create_profile(attrs \\ %{}) do %Profile{} |> Profile.changeset(attrs) |> Repo.insert() end def update_profile(%Profile{} = profile, attrs) do profile |> Profile.changeset(attrs) |> Repo.update() end def delete_profile(%Profile{} = profile) do Repo.delete(profile) end def delete_profile_by_name(name) do Repo.get_by!(Profile, %{name: name}) |> Repo.preload(:stats) |> Repo.delete() end def change_profile(%Profile{} = profile) do Profile.changeset(profile, %{}) end def page_of_stats(profile, page \\ 1, per_page \\ @days_per_page) def page_of_stats(%Profile{} = x, y, z), do: page_of_stats(x.id, y, z) def page_of_stats(profile_id, page, per_page) do Stat |> order_by(desc: :inserted_at) |> where(profile_id: ^profile_id) |> Pagination.page(page, per_page: per_page) end end

Next episode will add some view helpers to get auto-generated links and listing information.

(Full repo available for premium members)