Multiple nested forms error: Ash.Error.Framework.AssumptionFailed does not implement the `AshPhoenix.FormData.Error`

Hi,

First of all sorry, i don’t know how to put this scenario as title. I am working with forms contains this scenario.

  1. Create company with user. This one has manage_relationships which has_many with the :users relationship like this:
create :create_company_with_user do
      accept [:name, :registration_number]

      # argument :generate_hash?, :boolean, default: false

      argument :user, :map, allow_nil?: false

      # change {Mgx.Company.Changes.RegistrationNumber, generate_hash?: :generate_hash?}

      change manage_relationship(:user, :users, type: :create)
    end
  1. Now for the user primary action is this which has personal_information has has_one relationship with the user like this(From Ash authentication, I made another action)
create :register_with_password_with_personal_details do
      description "Register a new user with a email and password and personal information"

      primary? true

      argument :email, :ci_string do
        allow_nil? false
      end

      argument :password, :string do
        description "The proposed password for the user, in plain text."
        allow_nil? false
        constraints min_length: 8
        sensitive? true
      end

      argument :password_confirmation, :string do
        description "The proposed password for the user (again), in plain text."
        allow_nil? false
        sensitive? true
      end

      argument :personal_information, :map do
        allow_nil? false
      end

      validate match(:email, ~r/@/), message: "must be a valid email"

      # Sets the email from the argument
      change set_attribute(:email, arg(:email))

      # Hashes the provided password
      change AshAuthentication.Strategy.Password.HashPasswordChange

      # Generates an authentication token for the user
      change AshAuthentication.GenerateTokenChange

      # Manage the relationship with the personal information
      change manage_relationship(:personal_information, :personal_information,
               type: :direct_control
             )

      # validates that the password matches the confirmation
      validate AshAuthentication.Strategy.Password.PasswordConfirmationValidation

      metadata :token, :string do
        description "A JWT that can be used to authenticate the user."
        allow_nil? false
      end
    end

So my relationships are:

  1. company has_many users
  2. user has_one personal_information

I tried to mount the the form like this:

def mount(_params, _session, socket) do
    form =
      Mgx.Company.form_to_create_company_with_user(forms: [auto?: true])
      |> AshPhoenix.Form.add_form([:user])
      |> AshPhoenix.Form.add_form([:user, :personal_information])

    {:ok,
     assign(socket,
       form: to_form(form)
     )}
  end

Immediately I get this warning when mounted and registration fails when submitted:

warning] Unhandled error in form submission for Mgx.Accounts.User.register_with_password_with_personal_details

This error was unhandled because Ash.Error.Framework.AssumptionFailed does not implement the `AshPhoenix.FormData.Error` protocol.

** (Ash.Error.Framework.AssumptionFailed) Assumption failed: Action does not correlate with an authentication strategy

This should not be possible, please report a detailed bug at:

https://github.com/ash-project/ash/issues/new?assignees=&labels=bug%2C+needs+review&template=bug_report.md&title=


[warning] Unhandled error in form submission for Mgx.Accounts.User.register_with_password_with_personal_details

This error was unhandled because Ash.Error.Framework.AssumptionFailed does not implement the `AshPhoenix.FormData.Error` protocol.

** (Ash.Error.Framework.AssumptionFailed) Assumption failed: Action does not correlate with an authentication strategy

This should not be possible, please report a detailed bug at:

https://github.com/ash-project/ash/issues/new?assignees=&labels=bug%2C+needs+review&template=bug_report.md&title=

This form successfully render though:

<.simple_form for={@form} phx-change="validate" phx-submit="save">
      <.input field={@form[:name]} label="Company Name" />
      <.input field={@form[:registration_number]} label="Company Registration Number" />
      <.inputs_for :let={user} field={@form[:user]}>
        <.input field={user[:email]} label="Email" type="email" />
        <.inputs_for :let={personal_information} field={user[:personal_information]}>
          <.input field={personal_information[:first_name]} label="First Name" />
          <.input field={personal_information[:last_name]} label="Last Name" />
          <.input field={personal_information[:country_code]} label="Country Code" />
          <.input field={personal_information[:phone_number]} label="Phone Number" />
        </.inputs_for>
      </.inputs_for>

      <:actions>
        <.button class="w-full bg-[#262262] hover:bg-[#0C0944]">Register</.button>
      </:actions>
    </.simple_form>

Side Note: If I remove the manage_relationship of personal_information in the user, I can successfully submit the form. with company and user. while removing add_form([:user, :personal_information]

Maybe mount function I am assigning wrong? As always any help would be greatly appreciated thank you.

This is total Form I see when I submit:

 [error] Registration failed: %Phoenix.HTML.Form{
  source: #AshPhoenix.Form<
    resource: Mgx.Company.CompanyInformation,
    action: :create_company_with_user,
    type: :create,
    params: %{
      "name" => "Hatfield and Vega Associates",
      "registration_number" => "Gamble Cotton Trading",
      "user" => %{
        "_form_type" => "create",
        "_persistent_id" => "0",
        "_touched" => "_form_type,_persistent_id,_touched,_unused_email,email,personal_information",
        "email" => "rocysog@mailinator.com",
        "password" => "password",
        "password_confirmation" => "password",
        "personal_information" => %{
          "_form_type" => "create",
          "_persistent_id" => "0",
          "_touched" => "_form_type,_persistent_id,_touched,_unused_country_code,_unused_first_name,_unused_last_name,_unused_phone_number,country_code,first_name,last_name,phone_number",
          "country_code" => "Tenetur enim ipsam q",
          "first_name" => "Troy",
          "last_name" => "Lewis",
          "phone_number" => "+1 (358) 257-3731"
        }
      }
    },
    source: #Ash.Changeset<
      domain: Mgx.Company,
      action_type: :create,
      action: :create_company_with_user,
      attributes: %{
        name: "Hatfield and Vega Associates",
        status: :pending,
        registration_number: "Gamble Cotton Trading"
      },
      relationships: %{
        users: [
          {[
             %{
               "_form_type" => "create",
               "_persistent_id" => "0",
               "_touched" => "_form_type,_persistent_id,_touched,_unused_email,email,personal_information",
               "email" => "rocysog@mailinator.com",
               "password" => "password",
               "password_confirmation" => "password",
               "personal_information" => %{
                 "_form_type" => "create",
                 "_persistent_id" => "0",
                 "_touched" => "_form_type,_persistent_id,_touched,_unused_country_code,_unused_first_name,_unused_last_name,_unused_phone_number,country_code,first_name,last_name,phone_number",
                 "country_code" => "Tenetur enim ipsam q",
                 "first_name" => "Troy",
                 "last_name" => "Lewis",
                 "phone_number" => "+1 (358) 257-3731"
               }
             }
           ],
           [
             ignore?: false,
             on_missing: :ignore,
             on_match: :ignore,
             on_lookup: :ignore,
             on_no_match: :create,
             eager_validate_with: false,
             authorize?: true,
             meta: [id: :user],
             type: :create
           ]}
        ]
      },
      arguments: %{
        user: %{
          "_form_type" => "create",
          "_persistent_id" => "0",
          "_touched" => "_form_type,_persistent_id,_touched,_unused_email,email,personal_information",
          "email" => "rocysog@mailinator.com",
          "password" => "password",
          "password_confirmation" => "password",
          "personal_information" => %{
            "_form_type" => "create",
            "_persistent_id" => "0",
            "_touched" => "_form_type,_persistent_id,_touched,_unused_country_code,_unused_first_name,_unused_last_name,_unused_phone_number,country_code,first_name,last_name,phone_number",
            "country_code" => "Tenetur enim ipsam q",
            "first_name" => "Troy",
            "last_name" => "Lewis",
            "phone_number" => "+1 (358) 257-3731"
          }
        }
      },
      errors: [],
      data: #Mgx.Company.CompanyInformation<
        company_documents: #Ash.NotLoaded<:relationship, field: :company_documents>,
        company_tdls: #Ash.NotLoaded<:relationship, field: :company_tdls>,
        company_gasx_profile: #Ash.NotLoaded<:relationship, field: :company_gasx_profile>,
        company_lngx_profile: #Ash.NotLoaded<:relationship, field: :company_lngx_profile>,
        billing_address: #Ash.NotLoaded<:relationship, field: :billing_address>,
        business_address: #Ash.NotLoaded<:relationship, field: :business_address>,
        users: #Ash.NotLoaded<:relationship, field: :users>,
        __meta__: #Ecto.Schema.Metadata<:built, "company_informations">,
        id: nil,
        name: nil,
        registration_number: nil,
        website: nil,
        subscription_plan: nil,
        status: :pending,
        last_registration_submitted_at: nil,
        inserted_at: nil,
        updated_at: nil,
        aggregates: %{},
        calculations: %{},
        ...
      >,
      valid?: true
    >,
    name: "form",
    data: nil,
    form_keys: [
      user: [
        create_action: :register_with_password_with_personal_details,
        create_resource: Mgx.Accounts.User,
        type: :single,
        forms: [
          personal_information: [
            type: :single,
            forms: [],
            sparse?: false,
            managed_relationship: {Mgx.Accounts.User, :personal_information,
             [
               on_lookup: :ignore,
               on_no_match: {:create, :create},
               on_match: {:update, :update},
               on_missing: {:destroy, :destroy},
               type: :direct_control
             ]},
            must_load?: false,
            updater: #Function<36.69732248/1 in AshPhoenix.Form.Auto.related/3>
          ]
        ],
        sparse?: false,
        managed_relationship: {Mgx.Company.CompanyInformation, :users,
         [
           on_missing: :ignore,
           on_lookup: :ignore,
           on_no_match: {:create, :register_with_password_with_personal_details},
           on_match: :ignore,
           type: :create
         ]},
        must_load?: false
      ]
    ],
    forms: %{
      user: #AshPhoenix.Form<
        resource: Mgx.Accounts.User,
        action: :register_with_password_with_personal_details,
        type: :create,
        params: %{
          "_form_type" => "create",
          "_persistent_id" => "0",
          "_touched" => "_form_type,_persistent_id,_touched,_unused_email,email,personal_information",
          "email" => "rocysog@mailinator.com",
          "password" => "password",
          "password_confirmation" => "password",
          "personal_information" => %{
            "_form_type" => "create",
            "_persistent_id" => "0",
            "_touched" => "_form_type,_persistent_id,_touched,_unused_country_code,_unused_first_name,_unused_last_name,_unused_phone_number,country_code,first_name,last_name,phone_number",
            "country_code" => "Tenetur enim ipsam q",
            "first_name" => "Troy",
            "last_name" => "Lewis",
            "phone_number" => "+1 (358) 257-3731"
          }
        },
        source: #Ash.Changeset<
          domain: Mgx.Accounts,
          action_type: :create,
          action: :register_with_password_with_personal_details,
          attributes: %{
            role: :company_admin,
            email: #Ash.CiString<"rocysog@mailinator.com">
          },
          relationships: %{
            personal_information: [
              {[
                 %{
                   "_form_type" => "create",
                   "_persistent_id" => "0",
                   "_touched" => "_form_type,_persistent_id,_touched,_unused_country_code,_unused_first_name,_unused_last_name,_unused_phone_number,country_code,first_name,last_name,phone_number",
                   "country_code" => "Tenetur enim ipsam q",
                   "first_name" => "Troy",
                   "last_name" => "Lewis",
                   "phone_number" => "+1 (358) 257-3731"
                 }
               ],
               [
                 ignore?: false,
                 on_missing: :destroy,
                 on_match: :update,
                 on_lookup: :ignore,
                 on_no_match: :create,
                 eager_validate_with: false,
                 authorize?: true,
                 meta: [id: :personal_information],
                 type: :direct_control
               ]}
            ]
          },
          arguments: %{
       (truncated)

Those changes from ash authentication rely on knowing which authentication strategy contains the relevant configuration. by default it comes from the action name, but if you make a custom action you need to tell it still:

change set_context(%{strategy_name: :password}) that should go above the other changes.

It works. Thanks again Zach :smile:

1 Like