Search code examples
elixirphoenix-frameworkecto

Ecto changeset add multiple errors


I have a config map that I want to validate inside a changeset. The config has multiple keys and each of them has some constraints.

I could run a validator for each of them when data is received but this forces me to write a lot of code which can be done smarter so I'm running a loop and trying to gather all failed validations instead of running them one by one.

defp validate_config_flags(changeset) do
    config = get_field(changeset, :config)

    for  {k, v} <- config  do
        if !Map.has_key?(@config_flags, k) || !validate_config_flag(k, v) do
            add_error(changeset, :"#{k}", "Invalid value for `#{k}`")
        end
    end

    changeset
end

Obv my problem is that I return changeset even though certain fields fail and my question is related to that. How do I add more than one error message/failed validation to the result instead of returning at the first add_error somehow?


Solution

  • Most of the times when you want to repeatedly modify a term in Elixir, you're looking for Enum.reduce/3:

    defp validate_config_flags(changeset) do
      Enum.reduce(get_field(changeset, :config), changeset, fn {k, v}, changeset ->
        if !Map.has_key?(@config_flags, k) || !validate_config_flag(k, v) do
          add_error(changeset, :"#{k}", "Invalid value for `#{k}`")
        else
          changeset
        end
      end)
    end
    

    This code is equivalent to yours but it keeps track of the new changeset returned by add_error at every step, similar to the following if Elixir had mutable variables:

    for {k, v} <- config  do
      if !Map.has_key?(@config_flags, k) || !validate_config_flag(k, v) do
        changeset = add_error(changeset, :"#{k}", "Invalid value for `#{k}`")
      end
    end