Outputting multiple errors with put flash

Hey all!

I can’t seem to find anything that matches my use case, which seems kind of crazy. Maybe I’m just looking in the wrong place. I have an address form that is connected with an api that tells me if the address is real or not. If it isn’t real it sends back errors based on validation etc etc. These get returned in {:error, changeset}.

My issue is putting all of these errors on the screen at the same time, right now I can only seem to do it one at a time.

      {:error, %Ecto.Changeset{} = changeset} ->
        IO.inspect(changeset)
        conn
        |> put_flash(:error, changeset.errors)
        |> redirect(to: group_address_path(conn, :new, conn.assigns.current_user.group_id))

This is what I’m doing right now, where the redirect goes back to the page they were on when trying to submit the form. Of course, this doesn’t work when there are multiple errors. Is there anyway around this?

1 Like

I wonder if you need to use traverse_errors/2 to aggregate the errors.

Interesting, that looks like it could work. Does this mean I can’t put multiple errors (or flashes) on the screen at the same time without writing code on the client side as well?

You’d put a single “flash” that would contain all your errors, probably.

So the issue seems to be that I can’t access the mapping of the errors(or don’t know how).

For example, my changeset errors come in the form

[ geocoding: {“our application blah blah” , []} ]

but when I call traverse_errors/2 and inspect the message it’s just “our application blah blah”, which is all I want in the first place. Is there a way to strip the errors that doesn’t include pattern matching since we don’t know if the errors will always be formatted this way?

Maybe you can use something like

    Ecto.Changeset.traverse_errors(changeset, fn {message, opts} ->
      Enum.reduce(opts, message, fn {key, value}, acc ->
        String.replace(acc, "%{#{key}}", to_string(value))
      end)
    end)

It would return the errors in the changeset in the following form:

%{password: ["too short"], name: ["already exists", "error2"]}

Then you can build the flash message by going over the map:

# you can also use iolists to build the message
traversed_errors_map
|> Enum.map(fn {key, errors} ->
  "#{key}: #{Enum.join(errors, ", ")}"
end)
|> Enum.join("\n")

It would result in:

password: too short
name: already exists, error2

Thank you! This taught me a lot