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)
Learn how to build a pagination links helper to drop into your EEx templates in Part 2
2 Comments
Thanks for this screencast! I have a problem with this. If I preload in a query, the subquery for counting the results fails:
I've fixed this with a more costly query:
Do you have a better solution for this?
Thanks,
Adrián
These are relevant: