Packages

Adds callbacks/hooks to Ecto: `after_insert`, `after_update`, `after_delete`, `after_get`, `before_insert`, `before_update`, `before_delete`. Useful for setting virtual fields and centralising logic.

Current section

Files

Jump to
ecto_hooks lib ecto_hooks.ex
Raw

lib/ecto_hooks.ex

defmodule Ecto.Repo.Hooks do
@moduledoc """
When `use`-ed in a module that also `use`-es `Ecto.Repo`, augments the following
`Ecto.Repo` callbacks to provide user definable hooks following successful
execution.
Hooks to `MyApp.EctoSchema.after_get/1`:
- `all/2`
- `get/3`
- `get!/3`
- `get_by/3`
- `get_by!/3`
- `one/2`
- `one!/2`
Hooks to `MyApp.EctoSchema.after_delete/1`:
- `delete/2`
- `delete!/2`
Hooks to `MyApp.EctoSchema.after_insert/1`:
- `insert/2`
- `insert!/2`
Hooks to `MyApp.EctoSchema.after_update/1`:
- `update/2`
- `update!/2`
Hooks to `MyApp.EctoSchema.after_insert/1` or to `MyApp.Ecto.Schema.after_update/1`:
- `insert_or_update/2`
- `insert_or_update!/2`
Please note that the result of executing a hook is the result ultimately returned
a user, and thus you should aim to only modify a given database result.
Any results wrapped within an `{:ok, _}` or `{:error, _}` are also returned re-wrapped
as expected.
## Example usage:
```elixir
def MyApp.Repo do
use Ecto.Repo,
otp_app: :my_app,
adapter: Ecto.Adapters.Postgres
use Ecto.Repo.Hooks
end
def MyApp.User do
use Ecto.Changeset
schema "users" do
field :first_name, :string
field :last_name, :string
field :full_name, :string, virtual: true
end
def after_get(%__MODULE__{first_name: first_name, last_name: last_name} = user) do
%__MODULE__{user | full_name: first_name <> " " <> last_name}
end
end
```
"""
defmacro __using__(_opts) do
quote do
@hooks unquote(__MODULE__)
defoverridable all: 2,
delete: 2,
delete!: 2,
get: 3,
get!: 3,
get_by: 3,
get_by!: 3,
insert: 2,
insert!: 2,
insert_or_update: 2,
insert_or_update!: 2,
one: 2,
one!: 2,
update: 2,
update!: 2
def insert(query, opts) do
with {:ok, result} <- super(query, opts) do
{:ok, @hooks.after_insert(result)}
end
end
def insert!(query, opts) do
query
|> super(opts)
|> @hooks.after_insert
end
def update(query, opts) do
with {:ok, result} <- super(query, opts) do
{:ok, @hooks.after_update(result)}
end
end
def update!(query, opts) do
query
|> super(opts)
|> @hooks.after_update
end
def get(query, id, opts) do
with %{__meta__: %Ecto.Schema.Metadata{}} = result <- super(query, id, opts) do
@hooks.after_get(result)
end
end
def get!(query, id, opts) do
query
|> super(id, opts)
|> @hooks.after_get
end
def get_by(query, clauses, opts) do
with %{__meta__: %Ecto.Schema.Metadata{}} = result <- super(query, clauses, opts) do
@hooks.after_get(result)
end
end
def get_by!(query, clauses, opts) do
query
|> super(clauses, opts)
|> @hooks.after_get
end
def one(query, opts) do
with %{__meta__: %Ecto.Schema.Metadata{}} = result <- super(query, opts) do
@hooks.after_get(result)
end
end
def one!(query, opts) do
query
|> super(opts)
|> @hooks.after_get
end
def all(query, opts) do
query
|> super(opts)
|> Enum.map(&@hooks.after_get/1)
end
def delete(query, opts) do
with {:ok, result} <- super(query, opts) do
{:ok, @hooks.after_delete(result)}
end
end
def delete!(query, opts) do
query
|> super(opts)
|> @hooks.after_delete
end
def insert_or_update(
%Ecto.Changeset{data: %{__meta__: %{state: :built}}} = changeset,
opts
) do
with {:ok, result} <- super(changeset, opts) do
{:ok, @hooks.after_insert(result)}
end
end
def insert_or_update!(
%Ecto.Changeset{data: %{__meta__: %{state: :built}}} = changeset,
opts
) do
changeset
|> super(opts)
|> @hooks.after_insert
end
def insert_or_update(
%Ecto.Changeset{data: %{__meta__: %{state: :loaded}}} = changeset,
opts
) do
with {:ok, result} <- super(changeset, opts) do
{:ok, @hooks.after_update(result)}
end
end
def insert_or_update!(
%Ecto.Changeset{data: %{__meta__: %{state: :loaded}}} = changeset,
opts
) do
changeset
|> super(opts)
|> @hooks.after_update
end
def insert_or_update(changeset, opts) do
super(changeset, opts)
end
def insert_or_update!(changeset, opts) do
super(changeset, opts)
end
end
end
def after_insert(%schema{} = data) do
if function_exported?(schema, :after_insert, 1) do
schema.after_insert(data)
else
data
end
end
def after_insert(data) do
data
end
def after_update(%schema{} = data) do
if function_exported?(schema, :after_update, 1) do
schema.after_update(data)
else
data
end
end
def after_update(data) do
data
end
def after_get(%schema{} = data) do
if function_exported?(schema, :after_get, 1) do
schema.after_get(data)
else
data
end
end
def after_get(data) do
data
end
def after_delete(%schema{} = data) do
if function_exported?(schema, :after_delete, 1) do
schema.after_delete(data)
else
data
end
end
def after_delete(data) do
data
end
end