Current section
Files
Jump to
Current section
Files
lib/ecto_trim.ex
defmodule EctoTrim do
@moduledoc """
Ecto parameterized type that trims and normalizes whitespace on cast and dump.
## Options
- `:mode` — `:single_line` (default) or `:multi_line`
- `:single_line` — collapses all whitespace to single spaces
- `:multi_line` — preserves newlines but collapses 3+ consecutive newlines to 2
## Usage
schema "companies" do
field :name, EctoTrim
field :bio, EctoTrim, mode: :multi_line
end
"""
use Ecto.ParameterizedType
@type mode :: :single_line | :multi_line
@type params :: %{mode: mode()}
@impl true
@spec type(params()) :: :string
def type(_params), do: :string
@impl true
@spec init(keyword()) :: params()
def init(opts) do
mode = Keyword.get(opts, :mode, :single_line)
unless mode in [:single_line, :multi_line] do
raise ArgumentError, "EctoTrim :mode must be :single_line or :multi_line, got: #{inspect(mode)}"
end
%{mode: mode}
end
@impl true
@spec cast(term(), params()) :: {:ok, String.t() | nil} | :error
def cast(nil, _params), do: {:ok, nil}
def cast(str, %{mode: mode}) when is_binary(str) do
{:ok, str |> String.trim() |> normalize(mode)}
end
def cast(_, _params), do: :error
@impl true
@spec dump(term(), function(), params()) :: {:ok, String.t() | nil} | :error
def dump(nil, _dumper, _params), do: {:ok, nil}
def dump(str, _dumper, %{mode: mode}) when is_binary(str) do
{:ok, str |> String.trim() |> normalize(mode)}
end
@impl true
@spec load(term(), function(), params()) :: {:ok, String.t() | nil}
def load(nil, _loader, _params), do: {:ok, nil}
def load(str, _loader, _params) when is_binary(str), do: {:ok, str}
@spec normalize(String.t(), mode()) :: String.t()
defp normalize(str, :single_line), do: String.replace(str, ~r/\s+/, " ")
defp normalize(str, :multi_line), do: String.replace(str, ~r/\n{3,}/, "\n\n")
end