From 1d955661980cfb3b8206a0b6f52732971a0a6f04 Mon Sep 17 00:00:00 2001 From: Juan Martin Begalli Date: Tue, 24 Mar 2026 23:46:57 -0300 Subject: [PATCH] feat: add paginate_by_default? option to read actions --- .formatter.exs | 1 + documentation/dsls/DSL-Ash.Resource.md | 7 ++++--- lib/ash/actions/read/read.ex | 4 +++- lib/ash/resource/actions/read.ex | 7 +++++++ test/actions/pagination_test.exs | 16 ++++++++++++++++ 5 files changed, 31 insertions(+), 4 deletions(-) diff --git a/.formatter.exs b/.formatter.exs index d32fbcb088..11cf3376e3 100644 --- a/.formatter.exs +++ b/.formatter.exs @@ -223,6 +223,7 @@ spark_locals_without_parens = [ on: 1, only_when_valid?: 1, page: 1, + paginate_by_default?: 1, pagination: 0, pagination: 1, parse_attribute: 1, diff --git a/documentation/dsls/DSL-Ash.Resource.md b/documentation/dsls/DSL-Ash.Resource.md index 483eddbbea..ff48e661ae 100644 --- a/documentation/dsls/DSL-Ash.Resource.md +++ b/documentation/dsls/DSL-Ash.Resource.md @@ -1037,7 +1037,7 @@ prepare build(sort: [:foo, :bar]) | Name | Type | Default | Docs | |------|------|---------|------| -| [`on`](#actions-action-prepare-on){: #actions-action-prepare-on } | `:read \| :action \| list(:read \| :action)` | `[:read]` | The action types the preparation should run on. By default, preparations only run on read actions. Use `:action` to run on generic actions. | +| [`on`](#actions-action-prepare-on){: #actions-action-prepare-on } | `:read \| :action \| :create \| :update \| :destroy \| list(:read \| :action \| :create \| :update \| :destroy)` | `[:read]` | The action types the preparation should run on. By default, preparations only run on read actions. Use `:action` to run on generic actions. | | [`where`](#actions-action-prepare-where){: #actions-action-prepare-where } | `(any, any -> any) \| module \| list((any, any -> any) \| module)` | `[]` | Validations that should pass in order for this preparation to apply. Any of these validations failing will result in this preparation being ignored. | | [`only_when_valid?`](#actions-action-prepare-only_when_valid?){: #actions-action-prepare-only_when_valid? } | `boolean` | `false` | If the preparation should only run on valid queries. | @@ -1473,7 +1473,7 @@ prepare build(sort: [:foo, :bar]) | Name | Type | Default | Docs | |------|------|---------|------| -| [`on`](#actions-read-prepare-on){: #actions-read-prepare-on } | `:read \| :action \| list(:read \| :action)` | `[:read]` | The action types the preparation should run on. By default, preparations only run on read actions. Use `:action` to run on generic actions. | +| [`on`](#actions-read-prepare-on){: #actions-read-prepare-on } | `:read \| :action \| :create \| :update \| :destroy \| list(:read \| :action \| :create \| :update \| :destroy)` | `[:read]` | The action types the preparation should run on. By default, preparations only run on read actions. Use `:action` to run on generic actions. | | [`where`](#actions-read-prepare-where){: #actions-read-prepare-where } | `(any, any -> any) \| module \| list((any, any -> any) \| module)` | `[]` | Validations that should pass in order for this preparation to apply. Any of these validations failing will result in this preparation being ignored. | | [`only_when_valid?`](#actions-read-prepare-only_when_valid?){: #actions-read-prepare-only_when_valid? } | `boolean` | `false` | If the preparation should only run on valid queries. | @@ -1554,6 +1554,7 @@ Adds pagination options to a resource | [`max_page_size`](#actions-read-pagination-max_page_size){: #actions-read-pagination-max_page_size } | `pos_integer` | `250` | The maximum amount of records that can be requested in a single page | | [`stable_sort`](#actions-read-pagination-stable_sort){: #actions-read-pagination-stable_sort } | `any` | | A stable sort statement to add to a query (after any existing sorts). Only added if the sort does not already contain a stable sort (sorting on fields that uniquely identify a record). Defaults to the primary key. | | [`required?`](#actions-read-pagination-required?){: #actions-read-pagination-required? } | `boolean` | `true` | Whether or not pagination can be disabled (by passing `page: false` to `Ash.Api.read!/2`, or by having `required?: false, default_limit: nil` set). Only relevant if some pagination configuration is supplied. | +| [`paginate_by_default?`](#actions-read-pagination-paginate_by_default?){: #actions-read-pagination-paginate_by_default? } | `boolean` | `false` | Whether or not to paginate by default when pagination is not required and no page parameters are provided. | @@ -2699,7 +2700,7 @@ prepare build(sort: [:foo, :bar]) | Name | Type | Default | Docs | |------|------|---------|------| -| [`on`](#preparations-prepare-on){: #preparations-prepare-on } | `:read \| :action \| list(:read \| :action)` | `[:read]` | The action types the preparation should run on. By default, preparations only run on read actions. Use `:action` to run on generic actions. | +| [`on`](#preparations-prepare-on){: #preparations-prepare-on } | `:read \| :action \| :create \| :update \| :destroy \| list(:read \| :action \| :create \| :update \| :destroy)` | `[:read]` | The action types the preparation should run on. By default, preparations only run on read actions. Use `:action` to run on generic actions. | | [`where`](#preparations-prepare-where){: #preparations-prepare-where } | `(any, any -> any) \| module \| list((any, any -> any) \| module)` | `[]` | Validations that should pass in order for this preparation to apply. Any of these validations failing will result in this preparation being ignored. | | [`only_when_valid?`](#preparations-prepare-only_when_valid?){: #preparations-prepare-only_when_valid? } | `boolean` | `false` | If the preparation should only run on valid queries. | diff --git a/lib/ash/actions/read/read.ex b/lib/ash/actions/read/read.ex index 6c2b70f54e..1e99383b8f 100644 --- a/lib/ash/actions/read/read.ex +++ b/lib/ash/actions/read/read.ex @@ -3832,7 +3832,9 @@ defmodule Ash.Actions.Read do action.pagination.default_limit -> Keyword.put(page_opts, :limit, action.pagination.default_limit) - is_nil(page_opts) and action.pagination.required? and not relationship? -> + is_nil(page_opts) and + (action.pagination.required? or action.pagination.paginate_by_default?) and + not relationship? -> if action.pagination.default_limit do [limit: action.pagination.default_limit] else diff --git a/lib/ash/resource/actions/read.ex b/lib/ash/resource/actions/read.ex index be028d27c4..aa7d2993b8 100644 --- a/lib/ash/resource/actions/read.ex +++ b/lib/ash/resource/actions/read.ex @@ -149,6 +149,12 @@ defmodule Ash.Resource.Actions.Read do doc: "Whether or not pagination can be disabled (by passing `page: false` to `Ash.Api.read!/2`, or by having `required?: false, default_limit: nil` set). Only relevant if some pagination configuration is supplied.", default: true + ], + paginate_by_default?: [ + type: :boolean, + doc: + "Whether or not to paginate by default when pagination is not required and no page parameters are provided.", + default: false ] ] @@ -160,6 +166,7 @@ defmodule Ash.Resource.Actions.Read do countable: false, stable_sort: nil, required?: false, + paginate_by_default?: false, keyset?: false, offset?: false, __spark_metadata__: nil diff --git a/test/actions/pagination_test.exs b/test/actions/pagination_test.exs index 306db73775..e8be533023 100644 --- a/test/actions/pagination_test.exs +++ b/test/actions/pagination_test.exs @@ -64,6 +64,14 @@ defmodule Ash.Actions.PaginationTest do pagination offset?: true, countable: true, required?: false end + read :paginate_by_default do + pagination offset?: true, + countable: true, + required?: false, + paginate_by_default?: true, + default_limit: 3 + end + read :offset_countable_by_default do pagination offset?: true, countable: :by_default, required?: false end @@ -308,6 +316,14 @@ defmodule Ash.Actions.PaginationTest do assert %{results: [%{name: "3"}]} = Ash.page!(page, :self) end + + test "paginate_by_default? applies default limit when no page opts" do + assert %Ash.Page.Offset{results: results} = + User + |> Ash.read!(action: :paginate_by_default) + + assert Enum.count(results) == 3 + end end describe "keyset pagination with nil fields" do