Store a phone number in e164 format

A User resource has attribute :mobile_phone, :string, allow_nil?: false. The user can edit his/her phone number. The problem is that in Germany human users use different format to enter their phone number. But I want to save it in e164 format.

There is GitHub - ex-phone-number/ex_phone_number: Elixir port of libphonenumber which takes care of auto converting a string into e164 format.

I have to do the following code to format any string into e164:

{:ok, phone_number} = ExPhoneNumber.parse(mobile_phone, "DE")
ExPhoneNumber.format(phone_number, :e164)

How can I get the resource to automatically do that before validation kicks in or before it is saved?

Hello, I’m assuming that you want to store it in the database.

If you want to manipulate the data before it is saved and after it is fetched from DB, I would use the Ecto.Type behaviour to create your own custom field “Phone”. Here is the documentation.

You can take a look at this library ecto_phone_number | Hex to check if it does what you want. You can also read the definition of their custom Ecto.Type here as reference to implement yours.

1 Like

@rlopzc is correct except that you’ll want to us an Ash.Type in this instance :slight_smile: Often Ash.Type can be built to wrap another ecto type, for example:

defmodule YourApp.PhoneNumber do
  use Ash.Type

  def cast_input(value, constraints) do
    SomeEctoType.cast(value, constraints)
  end

  ...
end

and so on.

2 Likes

In case somebody searches for the solution:

defmodule YourApp.AshPhoneNumber do
  @moduledoc """
  PhoneNumber Type to store the number in a e164 format.
  """

  use Ash.Type

  @impl Ash.Type
  def storage_type(_), do: :string

  @impl Ash.Type
  def cast_input(nil, _), do: {:ok, nil}

  def cast_input(value, _) do
    ecto_type_cast = Ecto.Type.cast(:string, value)
    {:ok, raw_phone_number} = ecto_type_cast

    case ExPhoneNumber.parse(raw_phone_number, "DE") do
      {:ok, phone_number} ->
        case ExPhoneNumber.is_possible_number?(phone_number) do
          true -> {:ok, ExPhoneNumber.format(phone_number, :e164)}
          _ -> ecto_type_cast
        end

      _ ->
        ecto_type_cast
    end
  end

  @impl Ash.Type
  def cast_stored(nil, _), do: {:ok, nil}

  def cast_stored(value, _) do
    Ecto.Type.load(:string, value)
  end

  @impl Ash.Type
  def dump_to_native(nil, _), do: {:ok, nil}

  def dump_to_native(value, _) do
    Ecto.Type.dump(:string, value)
  end
end
3 Likes