if Code.ensure_loaded?(Ecto.Changeset) do defmodule Localize.Inputs.Number.Changeset do @moduledoc """ Ecto.Changeset helpers for `Localize.Inputs`. Only compiled when `:ecto` is loaded. schema "products" do field :quantity, :integer field :rating, :decimal end def changeset(product, attrs) do product |> Ecto.Changeset.cast(attrs, [:quantity, :rating]) |> Localize.Inputs.Number.Changeset.validate_number(:quantity, min: 1) |> Localize.Inputs.Number.Changeset.validate_number(:rating, min: 0, max: 5, decimals: 1) end """ alias Ecto.Changeset alias Localize.Inputs.Number.Validator @doc """ Validates a `Decimal` / integer field with the rules from `Localize.Inputs.Number.Validator.validate_number/2`. ### Arguments * `changeset` is an `Ecto.Changeset.t/0`. * `field` is the field name as an atom. * `options` is a keyword list of options forwarded to `Localize.Inputs.Number.Validator.validate_number/2`. ### Options * `:required` — when `true`, `nil` is rejected. * `:min` — minimum allowed value. * `:max` — maximum allowed value. * `:decimals` — maximum number of fractional digits. ### Returns * The changeset, with one error added per failing check. ### Examples iex> changeset = Ecto.Changeset.cast({%{}, %{quantity: :integer}}, %{"quantity" => 5}, [:quantity]) iex> changeset = Localize.Inputs.Number.Changeset.validate_number(changeset, :quantity, min: 1, max: 10) iex> changeset.valid? true """ @spec validate_number(Ecto.Changeset.t(), atom(), Keyword.t()) :: Ecto.Changeset.t() def validate_number(%Changeset{} = changeset, field, options \\ []) do value = Changeset.get_field(changeset, field) case Validator.validate_number(value, options) do :ok -> changeset {:error, %Localize.Inputs.ValidationError{errors: errors}} -> add_errors(changeset, field, errors) end end defp add_errors(changeset, field, errors) do Enum.reduce(errors, changeset, fn {kind, message}, acc -> Changeset.add_error(acc, field, message, validation: kind) end) end end end