Ignore negative integers in api request with Ecto

Hi,

I’m validating a request with Ecto.Changeset — Ecto v3.10.3 there are quite a few parameters and I want to go a bit further with the defaults for them. Let’s only consider size for now.

Suppose I configured a Struct with

defstruct size: 10
@types %{size: :integer}

and then use this with

    {%__MODULE__{}, @types}
    |> Ecto.Changeset.cast(params, [:size])
    |> Ecto.Changeset.apply_changes()

And specify size=not_a_number it defaults to 10 as I want. However, when the user specifies size=-1, this value is kept (as it is an integer). I want to silently pick the default value when size < 0.

Using :empty_values option in cast/4 does not work as that requires a list of all numbers below 0. The best I can come up with is go through the parameters after the changes are applied (should be simple with guards then) and replacing invalid values with the defaults.

Is there a better solution?

Depending on how you are keying the errors from validations, it could be done using Ecto.reset_fields/2

defmodule Example.MyStruct do
  defstruct size: 10
  @types %{size: :integer}

  def changeset(params) do
    {%__MODULE__{}, @types}
    |> Ecto.Changeset.cast(params, [:size])
    |> Ecto.Changeset.validate_number(:size, greater_than: 0)
    |> apply_changes_with_defaults()
  end

  defp apply_changes_with_defaults(%Ecto.Changeset{errors: errors} = changeset) do
    changeset
    |> Ecto.Changeset.apply_changes()
    |> Ecto.reset_fields(Keyword.keys(errors))
  end
end
1 Like

The docs on empty values specifically states the following:

Those are either values, which will be considered empty if they match, or a function that must return a boolean if the value is empty or not.

1 Like

For anyone looking for this:

    {%__MODULE__{}, @types}
    |> Ecto.Changeset.cast(params, [:size],
      empty_values: Ecto.Changeset.empty_values() ++ [&non_positive_integer?/1]
    )

  defp non_positive_integer?(value) do
    case Integer.parse(value) do
      {integer, ""} -> integer < 0
      _ -> true
    end
  end
2 Likes