8
votes

Does the Phoenix Framework employ any types of callback filter such as those found in Rails? I know it is possible to validate changesets, but I am looking for ways to implement actions such as before_create, before_save and after_commit.

2

2 Answers

13
votes

Ecto does: https://hexdocs.pm/ecto/#!Ecto.Model.Callbacks.html

They are sightly different from the Rails ones: they receive and must return changesets and must be used for data consistency (don't use them to send e-mails and what not).

13
votes

From Ecto 2.0, callbacks have been completely removed.

So how to handle callbacks now?. Here are two ways

For before_ callbacks you can use Changeset itself. One of the reasons callbacks where removed was because many developers relied on callbacks in many cases where changesets would suffice. So simply apply the required function to your changeset,

def changeset(post, params \\ :empty) do
  post
  |> cast(params, @required_params, @optional_params)
  |> validate_length(:title, min: 3)
  |> validate_length(:metadata, min: 3)
  |> implement_a_before_callback
end

def implement_a_before_callback(changeset)
   #Apply required actions and return Changeset
end

Another way, is to group multiple repo operations together using Ecto.Multi. From the docs

Ecto.Multi makes it possible to pack operations that should be performed together (in a single database transaction) and gives a way to introspect the queued operations without actually performing them. Each operation is given a name that is unique and will identify its result or will help to identify the place of failure in case it occurs. So whenever you want a group of data related operations to happen at once you could use Multi, both before_ and after_ callbacks can be substituted here.

An example would be

  # In defmodule Service
  def password_reset(account, params) do
    Multi.new
    |> Multi.update(:account, Account.password_reset_changeset(account, params))
    |> Multi.insert(:log, Log.password_reset_changeset(account, params))
    |> Multi.delete_all(:sessions, assoc(account, :sessions))
  end

Run it using

result = Repo.transaction(Service.password_reset(account, params))

You have to remember that you must execute data related queries and not do other tasks like send an email. For that you can simply pattern match on the result and perform appropriate action. Lets sat you wanted to send a mail if the transaction was successful and display some error message if not

case result do
  {:ok, %{account: account, log: log, sessions: sessions}} ->
    # Operation was successful, perform actions like sending a mail
  {:error, failed_operation, failed_value, changes_so_far} ->
    # One of the operations failed. Raise error message
end

Source: