tfwright / live_admin

Low-config admin UI for Phoenix apps, built on LiveView
MIT License
266 stars 27 forks source link

Handle ecto custom types by default #120

Closed jakespracher closed 1 month ago

jakespracher commented 1 month ago

Is it possible to handle custom Ecto type schema fields by default? I just added one and it broke my Live Admin UI for that schema (field cannot be set or updated).

https://hexdocs.pm/ecto/Ecto.Type.html

Need to figure out how to get it working again now.

tfwright commented 1 month ago

Hi @jakespracher if by "handle" you mean offer full support without any extra config, I don't think so, because there would be no way for LiveAdmin to know how the type should be treated, unless you have something specific in mind?

But to avoid having them break your instance I think just adding them to immutable_fields should do the trick (hidden_fields should also work but wouldn't allow you to even view the data). If that doesn't work, if you could post more info about how to replicate I can take a closer look.

https://hexdocs.pm/live_admin/LiveAdmin.html#base_configs_schema/0

jakespracher commented 1 month ago

It used to be a string field and could be updated as such, but the custom type adds validation. E.g.

defmodule App.Types.PhoneNumber do
  @moduledoc """
  An Ecto.Type for handling US phone numbers.

  This module provides custom casting, loading, and dumping functions for phone numbers,
  ensuring that they are properly formatted and validated as full US phone numbers.
  It uses the ExPhoneNumber library for parsing and formatting.

  The phone numbers are stored and returned in the E.164 format (e.g., "+12345678900").
  """

  use Ecto.Type
  require Logger

  @type t :: String.t()

  @impl true
  def type, do: :string

  @impl true
  def cast(phone_number) when is_binary(phone_number) do
    phone_number =
      if String.starts_with?(phone_number, "+1") do
        String.slice(phone_number, 2, String.length(phone_number) - 2)
      else
        phone_number
      end

    case ExPhoneNumber.parse(phone_number, "US") do
      {:ok, parsed} ->
        if full_us_number?(parsed) do
          {:ok, ExPhoneNumber.format(parsed, :e164)}
        else
          Logger.warning("Invalid US phone number format: #{phone_number}")
          :error
        end

      {:error, reason} ->
        Logger.warning("Failed to parse phone number '#{phone_number}': #{inspect(reason)}")
        :error
    end
  end

  def cast(_), do: :error

  @impl true
  def load(phone_number) when is_binary(phone_number) do
    {:ok, phone_number}
  end

  def load(_), do: :error

  @impl true
  def dump(phone_number) when is_binary(phone_number) do
    case cast(phone_number) do
      {:ok, formatted} -> {:ok, formatted}
      :error -> :error
    end
  end

  def dump(_), do: :error

  defp full_us_number?(%ExPhoneNumber.Model.PhoneNumber{} = parsed_number) do
    formatted = ExPhoneNumber.format(parsed_number, :e164)
    String.starts_with?(formatted, "+1") and String.length(formatted) == 12
  end
end

So I need to be able to teach LiveAdmin how to edit this field. Not clear on this from the linked function which I also saw in the readme

tfwright commented 1 month ago

I need to be able to teach LiveAdmin how to edit this field

This is currently only possible by overriding the form component.