Ecto.Changeset.traverse_errors pretty print with nested associations

Hi,

I come back to Elixir every now and then and I am a bit rusty when it comes to pattern matching on functions. I am trying to come up with an elegant solution that will traverse all changeset errors with nested associations. I had it working, but then came across a situation that tripped up my original code.


  # I want this
  %{
    order_products: [
      %{product_code: ["can't be blank"]},
      %{product_code: ["can't be blank"]}
    ],
    post_code: ["can't be blank"]
  }
  
  #To become this:
  %{
    product_code: "can't be blank",
    post_code: "can't be blank"
  }

 #This was my original code which builds upon the documenation for .traverse_errors(), but it breaks when there is an empty map for some entries e.g:
# %{
# order_products: [%{product_code: ["can't be blank"]}, %{}],
# post_code: ["can't be blank"]
# }

  def changeset_error_to_string(changeset) do

    Ecto.Changeset.traverse_errors(changeset, fn {msg, opts} ->
      Enum.reduce(opts, msg, fn {key, value}, acc ->
        String.replace(acc, "%{#{key}}", to_string(value))
      end)
    end)
    |> Enum.reduce("", fn {k, v}, acc ->
      extracted_errors = extract_errors(k, v)
      "#{acc}#{extracted_errors}"
    end)

  end

  # one level of nested extraction, not sure it would work for two levels
  def extract_errors(_, [errors = %{}]) do
    Enum.reduce(errors, "", fn {k, v}, acc ->
      extracted_errors = extract_errors(k, v)
      "#{acc}#{extracted_errors}"
    end)
  end

  def extract_errors(property, errors) when is_list(errors) do
    joined_errors = Enum.join(errors, "; ")
    "#{property}: #{joined_errors}<br>"
  end



Does anyone have a nice helper function that will deal neatly with a nested changeset

Seems like your requirements are quite specific to your needs. The example you’ve presented shows two errors for two separate structs, but it seems you want to “collapse” them.

Yes you are right. Wasn’t a great explanation there.

New day, I will try again!