page.ex (2895B)
1 # Zenflows is designed to implement the Valueflows vocabulary, 2 # written and maintained by srfsh <info@dyne.org>. 3 # Copyright (C) 2021-2023 Dyne.org foundation <foundation@dyne.org>. 4 # 5 # This program is free software: you can redistribute it and/or modify 6 # it under the terms of the GNU Affero General Public License as published by 7 # the Free Software Foundation, either version 3 of the License, or 8 # (at your option) any later version. 9 # 10 # This program is distributed in the hope that it will be useful, 11 # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 # GNU Affero General Public License for more details. 14 # 15 # You should have received a copy of the GNU Affero General Public License 16 # along with this program. If not, see <https://www.gnu.org/licenses/>. 17 18 defmodule Zenflows.DB.Page do 19 @moduledoc "Paging utilities to page results." 20 21 import Ecto.Query 22 23 alias Zenflows.DB.{ID, Repo} 24 25 @enforce_keys ~w[dir cur num filter]a 26 defstruct [:cur, :num, :filter, dir: :forw] 27 28 @typedoc """ 29 Represents a generic struct that has all required information to 30 (possibly filter and) paginate the rows of the database. 31 32 The fields are: 33 * `dir` - represents the *direction* that the database should query. 34 * `cur` - represents the *cursor* handle to use to paginate. 35 * `num` - represents the *number* of rows (+1) to fetch. 36 * `filter` - represents the filters to be used for querying. 37 """ 38 39 @type t() :: %__MODULE__{ 40 dir: :forw | :back, 41 cur: nil | ID.t(), 42 num: non_neg_integer(), 43 filter: nil | map(), 44 } 45 46 @doc """ 47 Parses a half-baked map/keyword into a `t:t()`. 48 49 You should use this function for functions that expect `t:t()`, and 50 you don't have any other means to generate a `t:t()` (such as with 51 `Zenflows.GQL.Connection.parse/2` that converts Relay-specific 52 paging information into `t:t()`). 53 """ 54 @spec new(map() | Keyword.t()) :: t() 55 def new(_ \\ %{}) 56 def new(params) when is_list(params), do: Map.new(params) |> new() 57 def new(params) when is_map(params) do 58 %__MODULE__{ 59 dir: params[:dir] || :forw, 60 cur: params[:cur], 61 num: params[:num] || Zenflows.GQL.Connection.def_page_size(), 62 filter: params[:filter], 63 } 64 end 65 66 @doc """ 67 Similar to `c:Ecto.Repo.all/2`, but the result is paginated. 68 69 Basically, transforms the queryable `q` into another query that 70 limits the amount of returned records according to `dir`, `cur` and 71 `num` (read `t:t()` on what those variables are used for). 72 """ 73 @spec all(Ecto.Queryable.t(), t()) :: [Ecto.Schema.t()] 74 def all(q, %{dir: dir, cur: cur, num: num}) do 75 order_by = 76 case dir do 77 :forw -> [asc: :id] 78 :back -> [desc: :id] 79 end 80 where = 81 case {dir, cur} do 82 {_, nil} -> [] 83 {:forw, cur} -> dynamic([x], x.id > ^cur) 84 {:back, cur} -> dynamic([x], x.id < ^cur) 85 end 86 from(x in q, 87 where: ^where, 88 order_by: ^order_by, 89 limit: ^num + 1, 90 select: x) 91 |> Repo.all() 92 end 93 end