Bylaw.Db.Adapters.Postgres.Checks.EctoChangesetCheckConstraints (bylaw_postgres v0.1.0-alpha.1)

Copy Markdown View Source

Validates Ecto.Changeset.check_constraint/3 annotations for Postgres checks.

Examples

With a check constraint on users.age, before:

def changeset(user, attrs) do
  user
  |> Ecto.Changeset.cast(attrs, [:age])
  |> Ecto.Changeset.validate_number(:age, greater_than_or_equal_to: 13)
end

The database still protects the invariant, but a constraint failure may reach callers as a database error instead of a changeset error attached to :age.

After, annotate the changeset with the matching check constraint:

def changeset(user, attrs) do
  user
  |> Ecto.Changeset.cast(attrs, [:age])
  |> Ecto.Changeset.validate_number(:age, greater_than_or_equal_to: 13)
  |> Ecto.Changeset.check_constraint(:age, name: :users_age_check)
end

Ecto can turn the database rejection into a normal changeset error.

Notes

The check skips dynamic cast or change field lists, check expressions without catalog column metadata, and constraints whose columns cannot be mapped to Ecto schema fields.

Options

The check discovers compiled Ecto schemas through reflection, parses source files for conservative changeset candidates, and only requires check_constraint/3 when Postgres can associate a check constraint with fields that a candidate casts. Dynamic cast/change field lists and check expressions without catalog column metadata are skipped for v1.

Pass paths: [...] so Bylaw can parse source AST for user-defined changeset functions:

{Bylaw.Db.Adapters.Postgres.Checks.EctoChangesetCheckConstraints,
 paths: ["lib/my_app"]}

When the repo can report config()[:otp_app], schema module discovery is derived from it. Use schema_modules: [...] when the check should inspect an explicit set of schemas instead:

{Bylaw.Db.Adapters.Postgres.Checks.EctoChangesetCheckConstraints,
 paths: ["lib/my_app/catalog"],
 schema_modules: [MyApp.Catalog.Product, MyApp.Catalog.Price]}

Use rules: [...] to scope the Postgres constraints considered by the check:

{Bylaw.Db.Adapters.Postgres.Checks.EctoChangesetCheckConstraints,
 paths: ["lib/my_app"],
 rules: [
   [
     only: [schema: "public"],
     except: [[table: "legacy_products", constraint: "legacy_price_check"]]
   ]
 ]}

Usage

Add this module to the checks passed to Bylaw.Db.Adapters.Postgres.validate/2. See the README usage section for the full ExUnit setup.

Summary

Functions

Implements the Bylaw.Db.Check validation callback.

Types

check_opt()

@type check_opt() ::
  {:validate, boolean()}
  | {:otp_app, atom()}
  | {:paths, [Path.t()]}
  | {:schema_modules, [module()]}
  | {:rules, [keyword()]}

check_opts()

@type check_opts() :: [check_opt()]

Functions

validate(target, opts)

@spec validate(target :: Bylaw.Db.Target.t(), opts :: check_opts()) ::
  Bylaw.Db.Check.result()

Implements the Bylaw.Db.Check validation callback.