AshPhoenix

repository·main·Indexed 41 days ago

https://github.com/ash-project/ash_phoenix

An integration package that bridges the Ash Framework with the Phoenix Framework. It provides specialized data structures for forms (AshPhoenix.Form), LiveView helpers, multitenancy tools via subdomains, and code generators for building Ash-based web applications.

Tokens
20.8K
Snippets
68
Records
88
Agent score
48%

What's inside AshPhoenix

  1. Overview of ash_phoenix features

    main

    The ash_phoenix package provides several key tools for building Ash-powered Phoenix applications:

    Form Handling

    • AshPhoenix.Form: A form data structure designed for using Ash resource actions within Phoenix forms.
    • AshPhoenix.Form.Auto: Automatically determines nested form structures based on manage_relationship calls within an action.
    • AshPhoenix.FilterForm: A data structure for building filter statements.
    • AshPhoenix.FormData.Error: A protocol that enables rendering Ash errors within Phoenix forms.

    LiveView & UI

    • AshPhoenix.LiveView: Helpers for querying data and integrating changes within Phoenix LiveView.
    • Phoenix.HTML.Safe implementations: Provides safe rendering for Ash.CiString, Ash.NotLoaded, and Decimal types.

    Multitenancy & Routing

    • AshPhoenix.SubdomainPlug: A Plug used to determine a tenant based on the request's subdomain.
    • AshPhoenix.LiveView.SubdomainHook: A hook for LiveView to determine a tenant using subdomains.

    Code Generation

    • mix ash_phoenix.gen.live: Generates LiveView modules.
    • mix ash_phoenix.gen.html: Generates Phoenix controllers and views.
  2. Configure positional arguments for form_to_* functions

    main

    By default, AshPhoenix ignores the args option in a resource's define block when building forms. To use positional arguments (which prevents certain values from being editable via the form), you must explicitly configure them in a forms block within the resource.

    This is useful for passing values like user_id that should be part of the action but should not be user-editable in the UI.

    # In your resource
    resource MyApp.Accounts.User do
      # ...
      forms do
        form :register_with_password, args: [:email]
      end
    end
    
    # Usage in your domain/controller
    MyApp.Accounts.form_to_register_with_password(email, ...opts)
  3. How nested forms work in AshPhoenix.Form

    main

    Nested or related forms are sets of inputs used for resource actions on related or embedded resources (e.g., a 'Business' form that includes multiple 'Location' forms).

    AshPhoenix.Form can automatically infer these nested structures by introspecting actions that use change manage_relationship. When an action manages a relationship, the form allows you to use Phoenix's <.inputs_for> component to render inputs for those related resources. To disable this automatic behavior, set forms: [auto?: false] when creating the form.

    # Example of an action that enables automatic nested form inference
    
    ```elixir
    # on a `MyApp.Operations.Business` resource
    create :create do
      accept [:name]
    
      argument :locations, {:array, :map}
    
      change manage_relationship(:locations, type: :create)
    end
    <%-- Using <.inputs_for> with the inferred 'locations' relationship --%
    <.simple_form for={@form} phx-change="validate" phx-submit="submit">
      <.input field={@form[:email]} />
    
      <.inputs_for :let={location} field={@form[:locations]}>
        <.input field={location[:name]} />
      </.inputs_for>
    </.simple_form>
  4. Understand the purpose of AshPhoenix

    main

    AshPhoenix is an integration package designed to bridge the Ash Framework with the Phoenix Framework. It enables developers to leverage Phoenix's UI capabilities (such as Forms and LiveViews) while utilizing Ash's data management features.

    Key integration points include:

    • AshPhoenix.Form: Tools for integrating Ash resources with Phoenix forms.
    • AshPhoenix.LiveView: Tools for integrating Ash resources with Phoenix LiveViews.
  5. How Union Forms work in AshPhoenix

    main

    When building a form for a union type, you use inputs_for as you normally would. AshPhoenix handles the underlying complexity of the union automatically.

    To track which member of the union is currently selected, AshPhoenix uses a hidden parameter named _union_type. You can use this parameter in your templates to conditionally render different input fields based on the active union type.

    <.inputs_for :let={fc} field={@form[:content]}>
      <!-- Use fc[:_union_type] to determine which fields to show -->
      <%= case fc.params["_union_type"] do %>
        <% "normal" -> %>
          <.input type="text" field={fc[:body]} />
        <% "special" -> %>
          <.input type="text" field={fc[:text]} />
      <% end %>
    </.inputs_for>
  6. Design resources to drive UI behavior

    main

    AshPhoenix is designed to be driven by your Ash resource configuration. To ensure forms and inputs work effectively, focus on defining well-structured resources. Specifically, ensure your resources have:

    • Appropriate validations to handle input errors.
    • Clear changes to manage data transformations and side effects.

    By making the resource the 'source of truth' for business logic, AshPhoenix can automatically derive much of the UI behavior.

  7. How custom input transformations affect forms

    main

    If your code interface defines custom_input with a transform block, the generated form functions will honor these transformations for the function arguments, but will NOT apply them to the params map.

    This means if an argument is transformed from a struct to an ID, the form will expect the ID for that argument, while the params map remains a raw key-value store of input data.

    # In your domain
    resource MyApp.Blog.Comment do
      define :create_with_post do
        action :create_with_post_id
        args [:post]
    
        custom_input :post, :struct do
          constraints instance_of: MyApp.Blog.Post
          transform to: :post_id, using: & &1.id
        end
      end
    end
    
    # Usage: The post struct is transformed to post_id in the form,
    # but the params remain unchanged.
    form = MyApp.Blog.form_to_create_with_post(
      %MyApp.Blog.Post{id: "some-id"},
      params: %{"text" => "Hello world"}
    )
  8. Handle form submission errors in LiveView

    main

    When using AshPhoenix.Form within a Phoenix LiveView, you should handle the result of AshPhoenix.Form.submit/2 to manage both successful submissions and validation errors.

    • On Success ({:ok, result}): Proceed with your success logic (e.g., redirecting or showing a success message).
    • On Error ({:error, form}): Re-assign the returned form to the socket. This updated form contains the validation error messages, which can then be rendered in your template to show users what went wrong.
    # In your LiveView
    def handle_event("submit", %{"form" => params}, socket) do
      case AshPhoenix.Form.submit(socket.assigns.form, params: params) do
        {:ok, post} ->
          # Success path
          {:noreply, success_path(socket, post)}
    
        {:error, form} ->
          # Show validation errors
          {:noreply, assign(socket, form: form)}
      end
    end