How to set arguments using ash_phoenix form?

I’m trying to use an update action taking an argument, but when I run it, it’s like the argument is never submitted. I’ve been debugging it for some time, and it works when I use the AshAdmin UI, but not from my application. I’m not quite sure what I’m missing?

Simplified update action

    update :update do
      argument :status, :atom do
        allow_nil? false
      end

      primary? true
    end

How I create the update form

    form =
      resource
      |> Form.for_update(:update, forms: [auto?: true])
      |> to_form()

The field used to set the status

              <.input
                name="status"
                type="select"
                field={@form[:status]}
                options={[
                  {gettext("In Progress"), :in_progress},
                  {gettext("Pending"), :pending},
                ]}
              />

An argument by itself is not doing anything. If you want to update the attribute status, you can use accept for that.

    update :update do
      accept [:status]

      primary? true
    end

You use arguments for values that aren’t attributes e.g. when accepting values you want to set on a related resource or if you want the user to input something you use to calculate attribute values.

    update :update do
      argument :status, :atom do
        allow_nil? false
      end

      change set_attribute(:status, arg(:status))

      primary? true
    end

Would also work but is unnecessary if you just accept the attribute

1 Like

My problem is that I try to update the state of the state machine with the line below inside the action. When I update the status from the admin view it actually gets updated, but when I do it from my form nothing happens. As far as I can tell, the argument is nil.

      change transition_state(arg(:status))

how do you submit the form? can you show your handle_event callback for that

Of course. I have a simplified version of the handle_event here, and I can share the full version if needed, it isn’t that pretty currently, though:

  def handle_event("save", params, socket) do

    action_opts = [actor: socket.assigns.current_user]

    case AshPhoenix.Form.submit(socket.assigns.form, params: params, action_opts: action_opts) do
      {:ok, _result} ->
        {:noreply, socket}

      {:error, form} ->
        {:noreply, assign(socket, :form, form)}
    end
  end

Have you dbgd or IO.inspected the params to ensure you get the right value?

The input does look right to me, but I haven’t done a lot of live_view yet. So just making sure.

Yes, I have inspected the params and would get something like this:

%{
  "status" => "in_progress",
}

And when inspecting the arguments of my form, I would even get:

%{status: :in_progress}

Can you make sure no error is returned from the submission? I checked the code in AshStateMachine, and everything you do looks correct. The only thing I can see happening is it returning a NoMatchingTransitionError

This is the form I got, when it failed. I have attached the whole thing this time.

[warning] Unhandled error in form submission for CaseManager.Cases.Case.update

This error was unhandled because AshStateMachine.Errors.NoMatchingTransition does not implement the `AshPhoenix.FormData.Error` protocol.

** (AshStateMachine.Errors.NoMatchingTransition) Attempted to change state from in_progress in action update, but no matching transition was configured.


%Phoenix.HTML.Form{
  source: #AshPhoenix.Form<
    resource: CaseManager.Cases.Case,
    action: :update,
    type: :update,
    params: %{
      case: %{
        :alert => ["fa4a8fbf-c590-40f1-981b-2680524a1220"],
        :escalated => false,
        "description" => "",
        "internal_note" => "",
        "priority" => "low",
        "status" => "t_positive",
        "title" => "wfwef"
      }
    },
    source: #Ash.Changeset<
      domain: CaseManager.Cases,
      action_type: :update,
      action: :update,
      attributes: %{},
      relationships: %{},
      arguments: %{},
      errors: [
        %AshStateMachine.Errors.NoMatchingTransition{
          action: :update,
          target: nil,
          old_state: :in_progress,
          splode: nil,
          bread_crumbs: [],
          vars: [],
          path: [],
          stacktrace: #Splode.Stacktrace<>,
          class: :invalid
        },
        %Ash.Error.Changes.Required{
          field: :status,
          type: :argument,
          resource: CaseManager.Cases.Case,
          splode: nil,
          bread_crumbs: [],
          vars: [],
          path: [],
          stacktrace: #Splode.Stacktrace<>,
          class: :invalid
        }
      ],
      data: #CaseManager.Cases.Case<
        file: #Ash.NotLoaded<:relationship, field: :file>,
        comment: #Ash.NotLoaded<:relationship, field: :comment>,
        alert: #Ash.NotLoaded<:relationship, field: :alert>,
        team: #CaseManager.Teams.Team<
          phone: #Ash.NotLoaded<:relationship, field: :phone>,
          email: #Ash.NotLoaded<:relationship, field: :email>,
          ip: #Ash.NotLoaded<:relationship, field: :ip>,
          case: #Ash.NotLoaded<:relationship, field: :case>,
          alert: #Ash.NotLoaded<:relationship, field: :alert>,
          __meta__: #Ecto.Schema.Metadata<:loaded, "team">,
          id: "b5b75885-c0b2-4eeb-a727-3a7e7c600968",
          name: "MSSP",
          type: :mssp,
          inserted_at: ~U[2024-11-12 19:36:33.585755Z],
          updated_at: ~U[2024-11-12 19:36:33.585755Z],
          aggregates: %{},
          calculations: %{},
          ...
        >,
        assignee: #Ash.NotLoaded<:relationship, field: :assignee>,
        reporter: #Ash.NotLoaded<:relationship, field: :reporter>,
        alert_join_assoc: #Ash.NotLoaded<:relationship, field: :alert_join_assoc>,
        __meta__: #Ecto.Schema.Metadata<:loaded, "case">,
        id: "962b7ccf-c986-4773-b0ea-e04a78babc65",
        title: "wfwef",
        description: nil,
        escalated: false,
        internal_note: nil,
        status: :in_progress,
        priority: :low,
        inserted_at: ~U[2024-11-13 19:35:14.158168Z],
        updated_at: ~U[2024-11-13 19:35:14.158168Z],
        reporter_id: "f2446fff-4df7-446e-b4eb-f35be7d5e2b3",
        assignee_id: nil,
        team_id: "b5b75885-c0b2-4eeb-a727-3a7e7c600968",
        aggregates: %{},
        calculations: %{},
        ...
      >,
      context: %{state_machine: %{attempted_change: nil}},
      valid?: false
    >,
    name: "form",
    data: #CaseManager.Cases.Case<
      file: #Ash.NotLoaded<:relationship, field: :file>,
      comment: #Ash.NotLoaded<:relationship, field: :comment>,
      alert: #Ash.NotLoaded<:relationship, field: :alert>,
      team: #CaseManager.Teams.Team<
        phone: #Ash.NotLoaded<:relationship, field: :phone>,
        email: #Ash.NotLoaded<:relationship, field: :email>,
        ip: #Ash.NotLoaded<:relationship, field: :ip>,
        case: #Ash.NotLoaded<:relationship, field: :case>,
        alert: #Ash.NotLoaded<:relationship, field: :alert>,
        __meta__: #Ecto.Schema.Metadata<:loaded, "team">,
        id: "b5b75885-c0b2-4eeb-a727-3a7e7c600968",
        name: "MSSP",
        type: :mssp,
        inserted_at: ~U[2024-11-12 19:36:33.585755Z],
        updated_at: ~U[2024-11-12 19:36:33.585755Z],
        aggregates: %{},
        calculations: %{},
        ...
      >,
      assignee: #Ash.NotLoaded<:relationship, field: :assignee>,
      reporter: #Ash.NotLoaded<:relationship, field: :reporter>,
      alert_join_assoc: #Ash.NotLoaded<:relationship, field: :alert_join_assoc>,
      __meta__: #Ecto.Schema.Metadata<:loaded, "case">,
      id: "962b7ccf-c986-4773-b0ea-e04a78babc65",
      title: "wfwef",
      description: nil,
      escalated: false,
      internal_note: nil,
      status: :in_progress,
      priority: :low,
      inserted_at: ~U[2024-11-13 19:35:14.158168Z],
      updated_at: ~U[2024-11-13 19:35:14.158168Z],
      reporter_id: "f2446fff-4df7-446e-b4eb-f35be7d5e2b3",
      assignee_id: nil,
      team_id: "b5b75885-c0b2-4eeb-a727-3a7e7c600968",
      aggregates: %{},
      calculations: %{},
      ...
    >,
    form_keys: [],
    forms: %{},
    domain: CaseManager.Cases,
    method: "put",
    submit_errors: [status: {"is required", []}],
    id: "form",
    transform_errors: nil,
    original_data: #CaseManager.Cases.Case<
      file: #Ash.NotLoaded<:relationship, field: :file>,
      comment: #Ash.NotLoaded<:relationship, field: :comment>,
      alert: #Ash.NotLoaded<:relationship, field: :alert>,
      team: #CaseManager.Teams.Team<
        phone: #Ash.NotLoaded<:relationship, field: :phone>,
        email: #Ash.NotLoaded<:relationship, field: :email>,
        ip: #Ash.NotLoaded<:relationship, field: :ip>,
        case: #Ash.NotLoaded<:relationship, field: :case>,
        alert: #Ash.NotLoaded<:relationship, field: :alert>,
        __meta__: #Ecto.Schema.Metadata<:loaded, "team">,
        id: "b5b75885-c0b2-4eeb-a727-3a7e7c600968",
        name: "MSSP",
        type: :mssp,
        inserted_at: ~U[2024-11-12 19:36:33.585755Z],
        updated_at: ~U[2024-11-12 19:36:33.585755Z],
        aggregates: %{},
        calculations: %{},
        ...
      >,
      assignee: #Ash.NotLoaded<:relationship, field: :assignee>,
      reporter: #Ash.NotLoaded<:relationship, field: :reporter>,
      alert_join_assoc: #Ash.NotLoaded<:relationship, field: :alert_join_assoc>,
      __meta__: #Ecto.Schema.Metadata<:loaded, "case">,
      id: "962b7ccf-c986-4773-b0ea-e04a78babc65",
      title: "wfwef",
      description: nil,
      escalated: false,
      internal_note: nil,
      status: :in_progress,
      priority: :low,
      inserted_at: ~U[2024-11-13 19:35:14.158168Z],
      updated_at: ~U[2024-11-13 19:35:14.158168Z],
      reporter_id: "f2446fff-4df7-446e-b4eb-f35be7d5e2b3",
      assignee_id: nil,
      team_id: "b5b75885-c0b2-4eeb-a727-3a7e7c600968",
      aggregates: %{},
      calculations: %{},
      ...
    >,
    transform_params: nil,
    prepare_params: nil,
    prepare_source: nil,
    raw_params: %{
      case: %{
        :alert => ["fa4a8fbf-c590-40f1-981b-2680524a1220"],
        :escalated => false,
        "description" => "",
        "internal_note" => "",
        "priority" => "low",
        "status" => "t_positive",
        "title" => "wfwef"
      }
    },
    warn_on_unhandled_errors?: true,
    any_removed?: false,
    added?: false,
    changed?: false,
    touched_forms: MapSet.new([:case, "_target", "description", "internal_note",
     "priority", "status", "title"]),
    valid?: false,
    errors: true,
    submitted_once?: true,
    just_submitted?: true,
    ...
  >,
  impl: Phoenix.HTML.FormData.AshPhoenix.Form,
  id: "form",
  name: "form",
  data: #CaseManager.Cases.Case<
    file: #Ash.NotLoaded<:relationship, field: :file>,
    comment: #Ash.NotLoaded<:relationship, field: :comment>,
    alert: #Ash.NotLoaded<:relationship, field: :alert>,
    team: #CaseManager.Teams.Team<
      phone: #Ash.NotLoaded<:relationship, field: :phone>,
      email: #Ash.NotLoaded<:relationship, field: :email>,
      ip: #Ash.NotLoaded<:relationship, field: :ip>,
      case: #Ash.NotLoaded<:relationship, field: :case>,
      alert: #Ash.NotLoaded<:relationship, field: :alert>,
      __meta__: #Ecto.Schema.Metadata<:loaded, "team">,
      id: "b5b75885-c0b2-4eeb-a727-3a7e7c600968",
      name: "MSSP",
      type: :mssp,
      inserted_at: ~U[2024-11-12 19:36:33.585755Z],
      updated_at: ~U[2024-11-12 19:36:33.585755Z],
      aggregates: %{},
      calculations: %{},
      ...
    >,
    assignee: #Ash.NotLoaded<:relationship, field: :assignee>,
    reporter: #Ash.NotLoaded<:relationship, field: :reporter>,
    alert_join_assoc: #Ash.NotLoaded<:relationship, field: :alert_join_assoc>,
    __meta__: #Ecto.Schema.Metadata<:loaded, "case">,
    id: "962b7ccf-c986-4773-b0ea-e04a78babc65",
    title: "wfwef",
    description: nil,
    escalated: false,
    internal_note: nil,
    status: :in_progress,
    priority: :low,
    inserted_at: ~U[2024-11-13 19:35:14.158168Z],
    updated_at: ~U[2024-11-13 19:35:14.158168Z],
    reporter_id: "f2446fff-4df7-446e-b4eb-f35be7d5e2b3",
    assignee_id: nil,
    team_id: "b5b75885-c0b2-4eeb-a727-3a7e7c600968",
    aggregates: %{},
    calculations: %{},
    ...
  >,
  action: nil,
  hidden: [
    _touched: "case,_target,description,internal_note,priority,status,title",
    _form_type: "update",
    id: "962b7ccf-c986-4773-b0ea-e04a78babc65"
  ],
  params: %{
    case: %{
      :alert => ["fa4a8fbf-c590-40f1-981b-2680524a1220"],
      :escalated => false,
      "description" => "",
      "internal_note" => "",
      "priority" => "low",
      "status" => "t_positive",
      "title" => "wfwef"
    }
  },
  errors: [status: {"is required", []}],
  options: [method: "put"],
  index: nil
}

It’s really strange that in your output, the only key inside all of the params/raw_params attributes is case.

Are you sure you are not doing anything else with params before submitting?

I just found the mistake. The mistake was when I created a case through a team relation, I did something like this:

    update :add_case do
      require_atomic? false

      argument :case, :map, allow_nil?: false

      change manage_relationship(:case, type: :create)
    end

Thus, it expects the case to be in the format %{case: case}, but when I updated the case I did it through the case resource which expects it as just a map with all the keys and values related to the attributes.

The error happened because there was a logic error in how I switched between these two formats. I don’t know if there is a smarter way of doing this, so I don’t have to switch between the two types of maps.

Thank you very much for your help and time! It’s really appreciated.

1 Like