Bylaw.Ecto.Query.Checks.ExplicitVisibilityPredicates
(bylaw_ecto_query v0.2.0)
Copy Markdown
View Source
Validates that configured visibility-sensitive fields are explicitly constrained.
This check is about query explicitness, not visibility correctness. Callers
configure the root fields that affect record visibility or lifecycle in their
application, such as :deleted_at, :archived_at, :hidden_at, :status,
:state, or :published_at. Bylaw only verifies that matching queries
mention those fields in supported root where predicates.
Examples
Bad:
from(Post, as: :post)
|> where([post: p], p.organization_id == ^organization_id)Why this is bad:
If Post is covered by rules: [where: [ecto_schemas: [Post]], fields: [:deleted_at]], this query does not say whether soft-deleted rows should be
visible. The visibility decision is left implicit.
Better:
from(Post, as: :post)
|> where([post: p], p.organization_id == ^organization_id)
|> where([post: p], is_nil(p.deleted_at))Why this is better:
The root predicate states the visibility decision directly: only rows without
deleted_at are requested.
Better when archived rows are intentional:
from(Post, as: :post)
|> where([post: p], p.archived_at <= ^cutoff)Notes
This check verifies explicitness, not visibility correctness. It accepts supported root predicates that mention configured fields, but it cannot prove predicates hidden inside fragments or subqueries.
The check is static. It accepts configured root fields when they appear
directly in where expressions, including is_nil(field), not is_nil(field),
bare field predicates, comparisons against values or parameters, and in
predicates whose right side has no field references. Field-to-field
comparisons are not treated as explicit constraints. It cannot prove visibility
fields hidden inside raw SQL fragments or subqueries. Combination queries such
as union, union_all, except, and intersect validate the parent query
and every combination branch independently.
When the root query uses an Ecto schema, configured fields are narrowed to
fields that exist on that schema. If no applicable configured fields remain,
the check returns :ok. Schema-less sources can still be validated because
there is no schema reflection signal.
Options
:validate- explicitfalsedisables this check. It can be used in the repo-wide check list or in call-site overrides passed toBylaw.Ecto.Query.validate/4.:rules- required rule keyword list or non-empty list of rule keyword lists. A single-rule shorthand such asrules: [fields: [:deleted_at]]is normalized to one rule.:fields- required non-empty list of visibility-sensitive root fields inside each rule.:whereand:except- optional rule matchers for scoping rules. Matchers use plural keys with list values, such asecto_schemas: [Post],tables: ["posts"],db_schemas: ["tenant_a"], andoperations: [:all].
This check requires :fields, so bare-module configuration is not valid.
Run globally:
{Bylaw.Ecto.Query.Checks.ExplicitVisibilityPredicates,
rules: [fields: [:deleted_at, :archived_at]]}Run only for matching rule scopes:
{Bylaw.Ecto.Query.Checks.ExplicitVisibilityPredicates,
rules: [
[where: [ecto_schemas: [Post]], fields: [:deleted_at, :archived_at]],
[where: [tables: ["comments"]], fields: [:deleted_at]]
]}Configure a different visibility field for one scope:
{Bylaw.Ecto.Query.Checks.ExplicitVisibilityPredicates,
rules: [where: [ecto_schemas: [PublishedPost]], fields: [:published_at]]}Usage
Add this module to the explicit check list passed through Bylaw.Ecto.Query.
See Bylaw.Ecto.Query for the full Ecto.Repo.prepare_query/3 setup.
Summary
Functions
Implements the Bylaw.Ecto.Query.Check validation callback.
Functions
@spec validate( Bylaw.Ecto.Query.Check.operation(), Bylaw.Ecto.Query.Check.query(), opts() ) :: Bylaw.Ecto.Query.Check.result()
Implements the Bylaw.Ecto.Query.Check validation callback.