How to validate that a decimal number is an integer in an Ecto changeset?

I’m using the Decimal package to work with a field in my changeset.

field :amount, :decimal

I can validate it with validate_number so that it should be greater or equal to a given number.

But for now I would like the user to submit an integer for this field (:amount).
I do not want to change the Ecto type to :integer but just want to have a decimal number with zero as decimal part.

Please any suggestion on how to achieve this with a custom Ecto validator, given that the field is of type :decimal?

Maybe you can check if it equals to the rounded version of itself.

defp validate_integer(changeset, field) do
  if integer?(get_field(changeset, field)) do
    changeset
  else
    add_error(changeset, field, "An integer is expected.")
  end
end

defp integer?(decimal) do
  decimal
  |> Decimal.round()
  |> Decimal.equal?(decimal)
end
2 Likes

Thank you @Aetherus!

it works fine. I just had to add one more clause to the integer?/1 function:

defp integer?(nil), do: false

otherwise the Decimal.round/1 function throws an exception when rendering a new empty form.

1 Like

My pleasure.

I was assuming that the field should not be nil and is validated with Ecto.Changeset.validate_required before piping into this validate_integer. My fault.

1 Like

Yes i used validate_required before piping into the integer validator. I think even with that, we need to check if the field is nil. Anyway thanks again. ^^

FWIW, Ecto.Changeset.validate_change would give you the "don’t bother doing this if the value is nil" behavior as well.

1 Like