Basic usage
Simply add use Lazarus to your repo module:
defmodule MyApp.Repo do
use Ecto.Repo, otp_app: :my_app, adapter: Ecto.Adapters.Postgres
use Lazarus
endThat does three things:
- Blocks direct
Repo.delete*andRepo.delete_all*calls unless the schema or table is bypassed - Adds explicit
Repo.soft_delete*andRepo.hard_delete*functions - Hides soft-deleted rows from normal reads and skips them in updates by default
Bypassing schemas and tables
If your repo absolutely NEEDS to leave specific schemas/tables alone, you can
whitelist schema modules and table names by passing in a list of schemas as
bypass_schemas and a list of table names as bypass_tables.
Bypassed sources are left alone by Lazarus. Reads, updates, and deletes against
them use ordinary Ecto behavior including Repo.delete, and Repo.delete_all
where those operations apply. These options were designed with third party
modules/libraries in mind that run queries, updates, and deletes on your repo
(e.g. when Oban reads from, updates, or deletes jobs from the oban_jobs
table).
When a bypassed source is the root of an Ecto query, Lazarus leaves that query alone, including fragments inside it. A bypassed join source is allowed, but it does not make unrelated fragments elsewhere in the query safe.
Bypasses do not apply to direct raw SQL calls such as Repo.query!/3 because
Lazarus cannot inspect contents of raw SQL queries. See
Overcoming Limitations for more info.
Example
use Lazarus,
bypass_schemas: [SessionToken],
bypass_tables: ["audit_logs"]Which option should you use?
bypass_schemaswhitelists schemas and their source tablesbypass_tableswhitelists tables only (useful for schema-less tables)
If you have a schema module SessionToken with a table "session_tokens",
bypassing just the schema module is sufficient enough, e.g.
bypass_schemas: [SessionToken]. Adding the table name to bypass_tables is
redundant.
If your database has a table and no schema for it, you can bypass it by name,
e.g. bypass_tables: ["audit_logs"].
If you have a schema for your table, and you decide to only bypass the table name and not the schema module itself (for whatever reason), you might end up with some inconsistent bypass results:
# Success (query operations)
Repo.update_all(
from(t in SessionToken, where: t.expires_at < ^DateTime.utc_now()),
set: [expired: true]
)
Repo.update_all(
from(t in "session_tokens", where: field(t, :expires_at) < ^DateTime.utc_now()),
set: [expired: true]
)
Repo.delete_all(from t in SessionToken, where: t.expires_at < ^DateTime.utc_now())
Repo.delete_all(from t in "session_tokens", where: field(t, :expires_at) < ^DateTime.utc_now())
# Raises (schemas, changesets)
Repo.delete(session_token)
Repo.delete(session_token_changeset)
Repo.delete_all(SessionToken)Real-world example with Oban
Bypasses Oban.Job schema with "oban_jobs" table, and a schema-less
"oban_peers" table
use Lazarus,
bypass_schemas: [Oban.Job],
bypass_tables: ["oban_peers"]