An Ecto.Type storing a Localize.Address.Address.t/0 as jsonb.
An address is stored as a single JSON object rather than as a column
per component. A postal address has many components and any given
address uses few of them — a Tokyo address and a Nebraska one
disagree about which fields even apply — so a wide table of mostly
empty columns is the wrong shape. Fields that are nil are omitted
from the stored object entirely, and a component added to the struct
later needs no migration.
Schema
jsonb is a built-in type, so nothing but the column is needed:
# in a migration
add :address, :map
# in a schema
field :address, Localize.Address.Ecto.Map.TypeOn PostgreSQL an Ecto :map column is jsonb, so the stored
address is queryable and indexable in SQL:
from c in Customer, where: fragment("? ->> 'city' = ?", c.address, "Tokyo")A GIN index over the column makes such lookups fast, and an expression index over a single component serves a frequently filtered one:
create index(:customers, ["(address ->> 'city')"])Ordering
Sort an address component the same way as any other text. Because
->> yields text, Localize.Ecto.Postgres.collate/2 from
localize_sql applies locale-aware ordering to it, which matters
for any locale whose collation differs from byte order.
Casting
cast/1 accepts an address struct, or a map keyed by either strings
or atoms — which is what an HTML form supplies. Unknown keys are
ignored, so a form carrying extra parameters does not fail. Parsing
an unstructured address string is Localize.Address.parse/2; it is
not attempted here, because a cast that silently invoked a parser
would make a form submission dependent on the libpostal NIF.