EctoMiddleware.Repo behaviour (ecto_middleware v2.1.0)

View Source

Enables middleware support for Ecto.Repo modules.

Add use EctoMiddleware.Repo to your Repo to enable middleware pipelines for database operations.

Setup

defmodule MyApp.Repo do
  use Ecto.Repo, otp_app: :my_app
  use EctoMiddleware.Repo

  @impl EctoMiddleware.Repo
  def middleware(action, resource) when is_insert(action, resource) do
    [NormalizeEmail, HashPassword, AuditLog]
  end

  def middleware(action, resource) when is_delete(action, resource) do
    [SoftDelete, AuditLog]
  end

  def middleware(_action, _resource) do
    [AuditLog]
  end
end

Defining Middleware

Implement the middleware/2 callback to specify which middleware run for each operation.

Pattern Matching on Actions

The first argument is the action atom (function name without arity):

@impl EctoMiddleware.Repo
def middleware(:insert, _), do: [ValidateEmail]

def middleware(:get, _), do: [EnrichData]

def middleware(:delete, _), do: [SoftDelete]

Available actions:

  • Read: :get, :get!, :get_by, :get_by!, :one, :one!, :all, :reload, :reload!, :preload
  • Write: :insert, :insert!, :update, :update!, :delete, :delete!, :insert_or_update, :insert_or_update!
  • Bulk: :insert_all, :update_all, :delete_all (opt-in, see below)

Bulk Operations

insert_all/3, update_all/3, and delete_all/2 are intercepted too, but middleware must opt in to run on them. This avoids passing a schema/source or queryable to middleware written for single-record changesets.

Opt a middleware in with the bulk_operations: true option:

defmodule AuditBulk do
  use EctoMiddleware, bulk_operations: true

  def process_before(resource, %{action: action}) when is_bulk_action(resource, action) do
    {:cont, resource}
  end

  def process_before(changeset, _resolution), do: {:cont, changeset}
end

Middleware that do not opt in are dropped from the chain for bulk actions, even when your middleware/2 returns them. Before callbacks receive the first argument to the Repo operation: the schema/source for insert_all, or an Ecto.Queryable for update_all and delete_all. The full argument list, including rows, updates, and options, is available in resolution.args. After callbacks receive {count, records_or_nil}.

The Repo's middleware/2 callback runs before this filter to choose the middleware list, even if no middleware have opted in. If your callback assumes write resources are structs or changesets, add a bulk clause before those clauses:

def middleware(action, resource) when is_bulk_action(resource, action), do: []

Return bulk-aware middleware instead of [] to enable middleware for these calls.

Pattern Matching on Resources

The second argument is the resource being operated on:

# Specific schema
def middleware(:insert, %User{}), do: [NormalizeEmail, HashPassword]

# Multiple schemas with same middleware
def middleware(:insert, %{__struct__: schema})
  when schema in [User, Admin], do: [AuditLog]

# Changesets
def middleware(:update, %Ecto.Changeset{data: %User{}}), do: [CheckOwnership]

Using Guards

You can use EctoMiddleware.Utils guards in your middleware/2 definitions:

@impl EctoMiddleware.Repo
def middleware(action, resource) when is_insert(action, resource) do
  [SetCreatedAt, AuditLog]
end

def middleware(action, resource) when is_update(action, resource) do
  [SetUpdatedAt, AuditLog]
end

def middleware(_action, _resource), do: []

See EctoMiddleware.Utils for available guards.

Default Middleware

Always provide a catch-all clause:

def middleware(_action, _resource), do: []

Middleware Execution

Middleware execute in the order specified. If any middleware returns {:halt, value}, execution stops immediately.

Summary

Functions

Enables the ability for a given Ecto.Repo to define and execute middleware.

Returns the configured middleware for the given repo.

Types

action()

@type action() ::
  :all
  | :delete!
  | :delete
  | :delete_all
  | :get!
  | :get
  | :get_by!
  | :get_by
  | :insert!
  | :insert
  | :insert_all
  | :insert_or_update!
  | :insert_or_update
  | :one!
  | :one
  | :reload!
  | :reload
  | :preload
  | :update!
  | :update
  | :update_all

middleware()

@type middleware() :: module()

resource()

@type resource() ::
  %Ecto.Queryable{}
  | %Ecto.Changeset{}
  | %{__meta__: Ecto.Schema.Metadata}
  | {%Ecto.Queryable{}, Keyword.t()}

Callbacks

middleware(action, resource)

@callback middleware(action :: action(), resource :: resource()) :: [middleware()]

Functions

__using__(opts)

(macro)

Enables the ability for a given Ecto.Repo to define and execute middleware.

middleware(repo, action, resource)

@spec middleware(repo :: module(), action(), resource()) :: [middleware()]

Returns the configured middleware for the given repo.