Aurora.Uix.Integration.Ash.Crud (Aurora UIX v0.1.4-rc.4)

Copy Markdown

Ash Framework implementation of CRUD operations.

Provides CRUD operations for Ash resources using the Ash Framework API. Supports both paginated and non-paginated listing, along with standard create, read, update, delete operations.

Key Features

  • Automatic action discovery and execution for Ash resources
  • Support for paginated and non-paginated queries
  • Query parsing with filters, sorting, and preloading
  • Primary action detection with fallback to first available action
  • AshPhoenix form integration for changesets
  • Actor threading for policy-protected resources via socket_opts/2 (see "Authorization" below)

Key Constraints

  • Requires valid Ash resource module with defined actions
  • Pagination requires Ash action configured with pagination option
  • Preloading handled differently: via Ash.Query.load for queries, Ecto repo for new structs

Authorization

Every CRUD call accepts an optional :actor keyword in its opts. When present (and non-nil) the actor is forwarded to the underlying Ash call (Ash.read/2, Ash.get/3, Ash.create/3, Ash.update/3, Ash.destroy/2, Ash.load/3, AshPhoenix.Form.for_update/3). When absent, no actor: is added — the host's Ash domain authorize config (default :by_default) decides whether policies run.

authorize?: is never set explicitly by this module. The actor is resolved at the call site by socket_opts/2, which reads CrudSpec.actor_assign (the atom configured via auix_resource_metadata ..., ash_actor_assign: :current_user) and pulls the actor from socket.assigns.

Resolution example

Given auix_resource_metadata :template, ash_resource: Tpl, ash_actor_assign: :current_user and a LiveView with socket.assigns.current_user = %User{id: 1}:

iex> socket_opts(crud_spec, socket)
[actor: %User{id: 1}]

Handlers extending Aurora UIX should not call socket_opts/2 directly — use Aurora.Uix.Templates.Basic.Helpers.backend_socket_opts/2 instead, which is backend-agnostic and accepts both full sockets and bare assigns maps.

See the Ash integration guide — Authorization & policies for the end-to-end worked example, alias (:actor_assign), behaviour matrix, and troubleshooting.

Summary

Functions

Creates an AshPhoenix form for updating an entity.

Creates a new resource in the database.

Default new function for initializing Ash resource structs.

Deletes a resource from the database.

Retrieves a single resource by ID.

Lists resources from an Ash action with optional query parameters.

Creates a new Ash resource struct with optional preloading.

Resolves per-call options from a LiveView socket for a given Ash CrudSpec.

Loads a specific page of results for paginated data.

Updates an existing resource in the database.

Functions

change(crud_spec, entity, form_name, attrs, opts \\ [])

Creates an AshPhoenix form for updating an entity.

Parameters

  • crud_spec (CrudSpec.t()) - The CrudSpec containing action configuration.
  • entity (struct()) - The Ash resource struct to update.
  • form_name (atom() | binary()) - Underlying form name.

  • attrs (map()) - Attributes to apply to the form.
  • opts (keyword()) - Additional options:

Returns

AshPhoenix.Form.t() - The form structure for the update operation.

Examples

iex> crud_spec = %CrudSpec{action: %{name: :update}}
iex> change(crud_spec, %MyApp.User{}, "user", %{name: "John"})
%AshPhoenix.Form{...}

create(crud_spec, params, opts \\ [])

Creates a new resource in the database.

Parameters

  • crud_spec (CrudSpec.t()) - The CrudSpec containing resource and action configuration.
  • params (map()) - Parameters for the new resource.
  • opts (keyword()) - Additional options:

Returns

tuple() - Result tuple, typically {:ok, struct()} or {:error, struct()}.

Examples

iex> crud_spec = %CrudSpec{resource: MyApp.User, action: %{name: :create}}
iex> create(crud_spec, %{name: "Alice", email: "alice@example.com"})
{:ok, %MyApp.User{name: "Alice", email: "alice@example.com"}}

default_new_function(entity, opts)

@spec default_new_function(struct(), keyword()) :: struct()

Default new function for initializing Ash resource structs.

Returns the entity struct as-is without modifications. This function serves as the default implementation for the :new_function operation when no custom function is provided via :ash_new_function option.

Parameters

  • entity (struct()) - The Ash resource struct to initialize.
  • opts (keyword()) - Options (currently unused).

Returns

struct() - The unmodified entity struct.

Examples

iex> post = %Post{status: :draft}
iex> default_new_function(post, [])
%Post{status: :draft}

delete(crud_spec, entity, opts \\ [])

Deletes a resource from the database.

Parameters

  • crud_spec (CrudSpec.t()) - The CrudSpec containing action configuration.
  • entity (struct()) - The resource to delete.
  • opts (keyword()) - Additional options:

Returns

tuple() - Result tuple, typically {:ok, struct()} or {:error, struct()}.

Examples

iex> crud_spec = %CrudSpec{action: %{name: :destroy}}
iex> delete(crud_spec, %MyApp.User{id: 1})
{:ok, %MyApp.User{id: 1}}

get(crud_spec, id, opts)

Retrieves a single resource by ID.

Parameters

  • crud_spec (CrudSpec.t()) - The CrudSpec containing resource and action configuration.
  • id (term()) - The resource identifier.
  • opts (keyword()) - Query options:
    • :actor (term()) - Actor forwarded to Ash.get/3 and Ash.load/3.
    • :preload (term()) - Associations to load.

Returns

struct() | nil - The matching resource or nil if not found or error occurs.

Examples

iex> crud_spec = %CrudSpec{resource: MyApp.User, action: %{name: :read}}
iex> get(crud_spec, "123", preload: [:posts])
%MyApp.User{id: "123", ...}

iex> get(crud_spec, "missing-id", [])
nil

list(definition, opts \\ [])

Lists resources from an Ash action with optional query parameters.

Parameters

  • crud_spec (CrudSpec.t()) - The CrudSpec containing resource and action configuration.
  • opts (keyword()) - Query options:
    • :actor (term()) - Actor forwarded to Ash.Query.for_read/3 and Ash.read/2 for policy-protected resources.
    • :where (list()) - Filter clauses.
    • :order_by (term()) - Sort specification.
    • :preload (term()) - Associations to load.
    • :paginate (Pagination.t()) - Pagination configuration (for paginated action).

Returns

Pagination.t() - %Pagination{} structure containing query results and metadata.

Examples

iex> crud_spec = %CrudSpec{resource: MyApp.User, action: %{name: :read, pagination: false}}
iex> list(crud_spec, where: [{:status, :eq, "active"}])
%Pagination{entries: [...], pages_count: 1, per_page: :infinity}

iex> crud_spec = %CrudSpec{resource: MyApp.Post, action: %{name: :read, pagination: true},
...>   auix_action_name: :list_function_paginated}
iex> list(crud_spec, paginate: %Pagination{page: 1, per_page: 20})
%Pagination{entries: [...], page: 1, pages_count: 5, per_page: 20}

new(crud_spec, attrs, opts)

Creates a new Ash resource struct with optional preloading.

Parameters

  • crud_spec (CrudSpec.t()) - The CrudSpec containing resource configuration.
  • attrs (map()) - Initial attributes for the new resource.
  • opts (keyword()) - Options:
    • :actor (term()) - Actor forwarded to Ash.load/3 during preload.
    • :preload (list()) - Associations to preload.

Returns

struct() - A new resource struct with the provided attributes and preloaded associations.

Examples

iex> crud_spec = %CrudSpec{resource: MyApp.User}
iex> new(crud_spec, %{name: "Jane"}, preload: [:profile])
%MyApp.User{name: "Jane", profile: %MyApp.Profile{}}

iex> new(crud_spec, %{title: "Hello"}, [])
%MyApp.Post{title: "Hello"}

socket_opts(crud_spec, arg2)

Resolves per-call options from a LiveView socket for a given Ash CrudSpec.

Reads crud_spec.actor_assign, looks up that key on socket.assigns, and returns [actor: actor] when the assign holds a non-nil value. Otherwise returns [].

Safe to merge into any CRUD call's opts via ++.

Parameters

  • crud_spec (CrudSpec.t() | term()) - The CrudSpec carrying :actor_assign. Non %CrudSpec{} values are treated as not-applicable and return [].

  • socket (Phoenix.LiveView.Socket.t() | map()) - A LiveView socket or any map with an :assigns field.

Returns

keyword() - [actor: actor] when configured and resolved, otherwise [].

Examples

iex> socket_opts(%CrudSpec{actor_assign: nil}, %{assigns: %{current_user: %{id: 1}}})
[]

iex> socket_opts(%CrudSpec{actor_assign: :current_user},
...>   %{assigns: %{current_user: %{id: 1}}})
[actor: %{id: 1}]

iex> socket_opts(%CrudSpec{actor_assign: :current_user}, %{assigns: %{}})
[]

to_page(crud_spec, pagination, page, opts \\ [])

Loads a specific page of results for paginated data.

Parameters

  • crud_spec (CrudSpec.t()) - The CrudSpec (currently unused for page bounds checking).
  • pagination (Pagination.t()) - The current pagination structure.
  • page (integer()) - The page number to load (must be >= 1 and <= pages_count).
  • opts (keyword()) - Additional options. Honours :actor for policy-protected reads.

Returns

Pagination.t() - Updated %Pagination{} structure with the requested page data, or unchanged pagination if page is out of bounds.

Examples

iex> crud_spec = %CrudSpec{resource: MyApp.User, action: %{name: :read}}
iex> to_page(crud_spec, %Pagination{page: 1, pages_count: 5, per_page: 20}, 3)
%Pagination{entries: [...], page: 3, pages_count: 5, per_page: 20}

iex> to_page(crud_spec, %Pagination{page: 1, pages_count: 5}, 10)
%Pagination{page: 1, pages_count: 5}

update(crud_spec, entity, params, opts \\ [])

Updates an existing resource in the database.

Parameters

  • crud_spec (CrudSpec.t()) - The CrudSpec containing action configuration.
  • entity (struct()) - The resource to update.
  • params (map()) - Parameters to update.
  • opts (keyword()) - Additional options:

Returns

tuple() - Result tuple, typically {:ok, struct()} or {:error, struct()}.

Examples

iex> crud_spec = %CrudSpec{action: %{name: :update}}
iex> update(crud_spec, %MyApp.User{id: 1}, %{name: "Bob"})
{:ok, %MyApp.User{id: 1, name: "Bob"}}