Using ex_money_sql with Ash

metlnerd
2023-05-14

metlnerd:

I’m hoping to use the Money type defined in the ex_money library as an attribute in Ash resources. This library has a companion, ex_money_sql that creates a Postgres migration and creates a custom type:

  def up do
    execute("CREATE TYPE public.money_with_currency AS (currency_code varchar, amount numeric);")
  end

In the documentation, there’s a Money struct, a :money_with_currency , a Money.Ecto.Composite.Type all used in various places. I’m very new to Elixir, Ash, and Ecto so it’s all a bit confusing.

I’m assuming I need a Custom Ash Type (right?), but not sure which identifiers mean what - for example, the existing examples for an Ash custom type all use an atom like :map or :string for storage_type , but it isn’t clear to me how they get defined, and if Ash would be able to see custom types in Postgres. Should I bypass the _sql library and roll my own :map -based solution?

ZachDaniel:

I believe using storage_type of :money_with_currency should pretty much do it for you as that will end up mapping to the exact sql type. There are a lot of layers there, basically because we build on top of ecto and they have some named storage types (like :map)

metlnerd:

Thanks, I ended up with this:

metlnerd:

defmodule Worthy.AshMoney do
  use Ash.Type

  @impl true
  def storage_type, do: :money_with_currency

  @impl true
  def cast_input(value, _) do
    Ecto.Type.cast(Money.Ecto.Composite.Type, value)
  end

  @impl true
  def cast_stored(value, _) do
    Ecto.Type.load(Money.Ecto.Composite.Type, value)
  end

  @impl true
  def dump_to_native(value, _) do
    Ecto.Type.dump(Money.Ecto.Composite.Type, value)
  end
end

ZachDaniel:

You’ll probably want to handle nil values in those callbacks

ZachDaniel:

Unlike ecto we give types control over nil values

metlnerd:

SG, thanks