Localize.Address.Ecto.Map.Type (Localize.Address v1.0.0)

Copy Markdown View Source

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.Type

On 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.