lib/cldr/unit/ecto/unit_with_usage_ecto_composite_type.ex

if Code.ensure_loaded?(Ecto.Type) do
  defmodule Cldr.UnitWithUsage.Ecto.Composite.Type do
    @moduledoc """
    Implements the Ecto.Type behaviour for a user-defined Postgres composite type
    called `:cldr_unit`.

    This is the preferred option for Postgres database since the serialized unit
    value is stored as a decimal number,
    """

    @behaviour Ecto.Type

    def type do
      :cldr_unit_with_usage
    end

    def blank?(_) do
      false
    end

    def load({unit_name, unit_value, nil}) do
      with {:ok, unit} <- Cldr.Unit.new(unit_name, unit_value) do
        {:ok, unit}
      else
        _ -> :error
      end
    end

    def load({unit_name, unit_value, unit_usage}) do
      with {:ok, unit} <- Cldr.Unit.new(unit_name, unit_value, usage: unit_usage) do
        {:ok, unit}
      else
        _ -> :error
      end
    end

    def dump(%Cldr.Unit{} = unit) do
      {:ok, {to_string(unit.unit), unit.value, to_string(unit.usage)}}
    end

    def dump(_) do
      :error
    end

    # Casting in changesets
    @dialyzer {:nowarn_function, {:cast, 1}}

    def cast(%Cldr.Unit{} = unit) do
      {:ok, unit}
    end

    def cast(%{"unit" => _, "value" => ""}) do
      {:ok, nil}
    end

    def cast(%{"unit" => unit_name, "value" => value, "usage" => usage})
        when (is_binary(unit_name) or is_atom(unit_name)) and is_number(value) do
      with decimal_value <- Decimal.new(value),
           {:ok, unit} <- Cldr.Unit.new(unit_name, decimal_value, usage: usage) do
        {:ok, unit}
      else
        {:error, {_, message}} -> {:error, message: message}
        :error -> {:error, message: "Couldn't cast value #{inspect value}"}
      end
    end

    def cast(%{"unit" => unit_name, "value" => value, "usage" => usage})
        when (is_binary(unit_name) or is_atom(unit_name)) and is_binary(value) do
      with {value, ""} <- Cldr.Decimal.parse(value),
           {:ok, unit} <- Cldr.Unit.new(unit_name, value, usage: usage) do
        {:ok, unit}
      else
        {:error, {_, message}} -> {:error, message: message}
        :error -> {:error, message: "Couldn't parse value #{inspect value}"}
      end
    end

    def cast(%{"unit" => unit_name, "value" => %Decimal{} = value, "usage" => usage})
        when is_binary(unit_name) or is_atom(unit_name) do
      with {:ok, unit} <- Cldr.Unit.new(unit_name, value, usage: usage) do
        {:ok, unit}
      else
        {:error, {_, message}} -> {:error, message: message}
      end
    end

    def cast(%{unit: unit_name, value: value} = unit) do
      cast(%{"unit" => unit_name, "value" => value, "usage" => unit.usage})
    end

    def cast(_money) do
      :error
    end

    # New for ecto_sql 3.2
    def embed_as(_), do: :self

    def equal?(%Cldr.Unit{} = term1, %Cldr.Unit{} = term2) do
      case Cldr.Unit.compare(term1, term2) do
        :eq -> true
        _ -> false
      end
    end

    def equal?(term1, term2), do: term1 == term2
  end
end