defmodule JediHelpers.ChangesetHelpers do @moduledoc """ Provides helper functions for trimming whitespace and validating string fields in Ecto changesets. Particularly useful for ensuring uniqueness and formatting of string inputs before applying database constraints. """ import Ecto.Changeset @field_type :string @doc """ Trims leading and trailing whitespace from one or more string fields in the changeset. Ensures consistency and helps maintain uniqueness constraints (e.g., on `citext` fields). ## Options - `:max` (`integer`): Maximum allowed length after trimming. If exceeded, a validation error is added. Default is 255. - `:enforce_unique` (`boolean`): When set to `true`, adds a `unique_constraint/3` to the field. Default is `false`. ## Examples changeset |> trim_whitespace(:username, max: 50, enforce_unique: true) changeset |> trim_whitespace(:username, enforce_unique: true) ## Parameters - `changeset` (`Ecto.Changeset.t()`): The changeset containing the field(s) to be processed. - `field` (`atom()` or `[atom()]`): The field(s) to trim. - `opts` (`keyword()`): Options for trimming and validation. ## Returns - An updated `Ecto.Changeset.t()` with trimmed values and optional validations. """ @spec trim_whitespace(Ecto.Changeset.t(), atom() | [atom()], keyword()) :: Ecto.Changeset.t() def trim_whitespace(changeset, keys, opts \\ []) def trim_whitespace(changeset, key, opts) when is_atom(key) do max = Keyword.get(opts, :max, 255) enforce_unique? = Keyword.get(opts, :enforce_unique, false) cond do Map.get(changeset.types, key) == @field_type -> changeset |> update_change(key, &String.trim/1) |> maybe_enforce_unique(key, enforce_unique?) |> validate_length(key, max: max) true -> changeset end end def trim_whitespace(changeset, keys, opts) when is_list(keys) do Enum.reduce(keys, changeset, fn key, acc -> trim_whitespace(acc, key, opts) end) end defp maybe_enforce_unique(changeset, key, true), do: unique_constraint(changeset, key) defp maybe_enforce_unique(changeset, _key, false), do: changeset end