Bylaw.Ecto.Query.Checks.HardDeleteOnSoftDeleteSchema (bylaw_ecto_query v0.1.0-alpha.1)

Copy Markdown View Source

Validates that soft-delete schemas are not hard-deleted with delete_all.

Schemas that declare a persisted :deleted_at or :archived_at field usually expect lifecycle removal to be represented as an update. A bulk hard delete on that schema is therefore suspicious even when the query is otherwise bounded.

Examples

Bad:

query =
  from(Post, as: :post)
  |> where([post: p], p.status == ^:archived)

Repo.delete_all(query)

Why this is bad:

If Post has a persisted :deleted_at or :archived_at field, hard-deleting rows bypasses the lifecycle model and permanently removes records the schema normally treats as soft-deletable.

Better:

Prefer Repo.update_all/3 setting :deleted_at or :archived_at instead of Repo.delete_all/2 when this check reports an issue.

query =
  from(Post, as: :post)
  |> where([post: p], p.status == ^:archived)

Repo.update_all(query, set: [deleted_at: DateTime.utc_now()])

Why this is better:

Removal is represented in the soft-delete field, preserving the schema's lifecycle convention.

Notes

This check uses persisted root schema fields as the signal. It ignores schema-less queries, virtual fields, source subqueries, and soft-delete fields on joined schemas.

The check is zero-config. Field presence in __schema__(:fields) is the signal.

Options

  • :validate - explicit false disables the check. Defaults to true.

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.

The root query and every combination branch are inspected independently. The check ignores schema-less queries, non-query values, virtual fields, source subqueries, and soft-delete fields that appear only on joined schemas.

Summary

Functions

validate(operation, query, opts)

Implements the Bylaw.Ecto.Query.Check validation callback.