/ecto_hooks

Ecto addon that re-implements Ecto.Model callbacks

Primary LanguageElixirMIT LicenseMIT

EctoHooks

Installation

Add :ecto_hooks to the list of dependencies in mix.exs:

def deps do
  [
    {:ecto_hooks, "~> 1.0.1"}
  ]
end

About

In the past, Ecto provided a series of automatic callbacks which would automatically be executed in response to CRUD-ing data.

While these callbacks were removed alongside Ecto.Model for various reasons, in some situations, they can still prove to be extremely useful for things such as centralizing the setting of virtual fields, or firing :telemetry events.

This library tries to experimentally bring back support for these "hooks" in a transparent and admittedly auto-magical way. Here is a minimal example:

# 1) Replace your app's Repo initialization with one of the two following options:

# * Just `use EctoHooks`. This won't work if you've overridden any Ecto.Repo functions yourself
def MyApp.Repo do
  use Ecto.Repo,
    otp_app: :my_app,
    adapter: Ecto.Adapters.Postgres

  use EctoHooks
end

# * Replace `use Ecto.Repo` with `use EctoHooks.Repo`. This should work no matter what
def MyApp.Repo do
  use EctoHooks.Repo,
    otp_app: :my_app,
    adapter: Ecto.Adapters.Postgres
end

# 2) Define some hooks in any Ecto.Schema module
def MyApp.User do
  use Ecto.Schema
  require Logger

  schema "users" do
    field :first_name, :string
    field :last_name, :string

    field :full_name, :string, virtual: true
  end

  def before_insert(%Ecto.Changeset{} = changeset) do
    Logger.info("inserting new user")
    changeset
  end

  def after_get(%__MODULE__{} = user, %EctoHooks.Delta{}) do
    %__MODULE__{user | full_name: user.first_name <> " " <> user.last_name}
  end
end

EctoHooks supports two classes of hook, before_* hooks and after_* hooks. All before_* hooks are arity-one and allow you to mutate, introspect, or change a given queryable before passing it onto Ecto.Repo; all after_* hooks are arity-two and allow you to mutate, introspect, or change the result of your hooked Ecto.Repo callback.

For after_* hooks, importantly, a EctoHooks.Delta struct is passed in as a second argument, which allows you to reflect on various pieces of metadata about the action which triggered the hook. This allows you to only run hooks if certain fields are changed, amongst other things. Please see the documentations re: EctoHooks.Delta for more information.

Likewise, for a list of supported hooks and their behaviours, please see the latest docs.

Links