Accrue.Ecto.Money (accrue v0.3.0)

Copy Markdown View Source

Two storage shapes for %Accrue.Money{} in Ecto schemas — both shipped in Phase 1 per D-02 and RESEARCH Open Question 5.

1. Custom Ecto.Type — single-column jsonb form

For places where a money value is one of many properties inside a jsonb blob (e.g., accrue_events.data), this module implements the Ecto.Type callbacks and serializes as %{"amount_minor" => integer, "currency" => string}.

field :snapshot, Accrue.Ecto.Money

This form is convenient but is NOT the canonical storage shape for first-class money columns. Use money_field/1 for those.

2. money_field/1 macro — two-column canonical form (D-02)

The canonical form: one money value expands to TWO physical Ecto fields plus a virtual accessor.

defmodule MyApp.Billing.Subscription do
  use Ecto.Schema
  import Accrue.Ecto.Money, only: [money_field: 1]

  schema "subscriptions" do
    money_field :price
    timestamps()
  end
end

That expansion produces:

field :price_amount_minor, :integer
field :price_currency,     :string
field :price,              :any, virtual: true

On load, a changeset/2 or post-load helper (Phase 2 wires this) sets the virtual :price field via Accrue.Money.new(row.price_amount_minor, String.to_existing_atom(row.price_currency)) so callers see a %Accrue.Money{} struct.

This two-column shape plays well with zero-decimal (JPY) and three-decimal (KWD) currencies, is indexable, and is analytics-friendly — see Pitfall #1 in 01-RESEARCH.md for why we reject the ex_money Postgres composite type as a canonical storage shape.

Summary

Functions

Callback implementation for Ecto.Type.embed_as/1.

Callback implementation for Ecto.Type.equal?/2.

Two-column macro — canonical storage shape per D-02.

Functions

embed_as(_)

Callback implementation for Ecto.Type.embed_as/1.

equal?(term1, term2)

Callback implementation for Ecto.Type.equal?/2.

money_field(name)

(macro)

Two-column macro — canonical storage shape per D-02.