Nested association errors won't render in nested form

ecto
phoenix
#1

Hello. I’m having some trouble getting errors to render inside a nested form.

I have the following:

<%= form_for @changeset, signup_path(@conn, :create), fn f -> %>

  <%= hidden_input f, :plan, value: @plan %>
  <%= hidden_input f, :channel, id: "channel" %>

  <%= inputs_for f, :owner, fn u -> %>

      <%= text_input u, :email, class: "form-control", required: true, readonly: true, style: "background-color:#f2f2f2" %>
      <%= label u, "Email (you can change this later)" %>
      <%= error_tag u, :email %>

      <%= text_input u, :first_name, autofocus: true, class: "form-control", required: true %>
      <%= label u, :first_name %>
      <%= error_tag u, :first_name %>

      <%= text_input u, :last_name, class: "form-control", required: true %>
      <%= label u, :last_name %>
      <%= error_tag u, :last_name %>

      <%= password_input u, :password, class: "form-control", pattern: ".{8,}", title: "Password must have 8 or more characters.", required: true %>
      <%= label u, "Password (minimum 8 characters)" %>
      <%= error_tag u, :password %>

      <%= password_input u, :password_confirmation, class: "form-control", required: true %>
      <%= label u, :password_confirmation %>
      <%= error_tag u, :password_confirmation %>

  <% end %> 

  <%= text_input f, :name, class: "form-control", required: true %>
  <%= label f, :organization_name %>
  <%= error_tag f, :name %>

  <%= submit "Finish", class: "btn btn-block btn-lg" %>

<% end %>

Pretty straightforward. There is an organization parent entity, and it has an owner child entity. When the user is creating their account, I allow them to enter info about both, and create and associate them behind the scenes.

The “happy path” works. But when the changeset is invalid, the errors don’t get rendered on the form. Below is an example changeset where the user did not enter their password confirmation correctly:

#Ecto.Changeset<
  action: nil,
  changes: %{
    name: "testtest",
    owner: #Ecto.Changeset<
      action: :insert,
      changes: %{
        email: "someemail@fastmail.com",
        first_name: "sdf",
        last_name: "asdf",
        password: "testtest",
        password_confirmation: "testtest1"
      },
      errors: [
        password_confirmation: {"does not match password",
         [validation: :confirmation]}
      ],
      data: #MyApp.Accounts.User<>,
      valid?: false
    >
  },
  errors: [],
  data: #MyApp.Organizations.Organization<>,
  valid?: false
>

The controller action’s relevant portion:

{:error, _operation, changeset, _changes} ->
  conn
  |> put_status(:unprocessable_entity)
  |> put_flash(:error, "Hmm, that didn't work. Please see the errors below.")
  |> render("create_account.html", plan: plan.description, changeset: changeset, channel: channel, title: "MyApp | Create Account")

What is missing here?

The changeset appears to be structured correctly, in the sense that organization is the parent entity and owner is the child entity. This mirrors the structure of the form. So, shouldn’t the error_tag corresponding to the Password Confirmation field render that field’s error?

#2

I could only guess that the template hides the error tags. Have you inspected the DOM after an error?

#3

So I did a small follow up on this issue because I personally have experienced it. The "problem " is that the action is set to :ignore or nil and the errors aren’t shown when rerendering. Check the docs here:

https://hexdocs.pm/phoenix_html/Phoenix.HTML.Form.html#module-a-note-on-errors

I had been fixing it manually changing the action with apply_action to :insert but the question still remain: why is the action not been set properly even when Repo.insert has been called?

I followed the Repo.insert code flow and seams to apply action in every branch of the flow so I will try to dig deeper.

Meanwhile try updating the action manually just before rendering.

Beat regards

1 Like
#4

Yes, that is how I ended up resolving the issue: by setting action on the changeset to :insert manually before render.

I, too, wonder why it is necessary. Something doesn’t seem right.

|> Ecto.Multi.run(:organization, fn(_repo, _result) ->
  %Organization{}
  |> Organization.create_organization_changeset(attrs)
  |> Repo.insert(prefix: TenantActions.build_prefix(tenant))
end)
...
|> Repo.transaction()
def create_organization_changeset(organization, attrs \\ %{}) do
  organization
  |> cast(attrs, [:owner_id, :channel, :name, :website, :physical_address, :mailing_address, :licensure_info, :logo_url, :phone])
  |> cast_assoc(:owner, with: &Accounts.User.register_user_changeset/2, required: true)
  |> validate_required([:name])
end
#5

Are you guys using the latest Phoenix (1.4.6 as of the time of this comment)?

#6

Yes, I’m on 1.4.6.

#7

I am using:


* phoenix 1.3.4 (Hex package) (mix)
  locked at 1.3.4 (phoenix) aaa1b55e
* phoenix_html 2.13.2 (Hex package) (mix)
  locked at 2.13.2 (phoenix_html) f5d27c9b
* phoenix_ecto 3.6.0 (Hex package) (mix)
  locked at 3.6.0 (phoenix_ecto) d65dbced