Feedback on Phoenix callbacks please

ecto
phoenix

#1

Good morning guys,

I’m starting with Phoenix after working with Rails for many years. I wrote an article about callbacks, or rather the lack of them, in Phoenix. I would love to get feedback from developers more experienced with the technology. https://medium.com/@kdiogenes/phoenix-callbacks-4a691760acdb?fbclid=IwAR09CIIyybXzsR5SuemHibjBb1V4TBMGyiJW_3ScxPp1hReBX1fdjUzQUMk

Hugs!


#2

Hello, there is a post here Before create callback in Ecto. It’s probably more related to Ecto than to Phoenix.

Ecto is a little bit different, as there is no callbacks. But as You did allow to mimic before callback. Ecto.Multi is used to perform multiple operation in a single transaction. Which could be seen as after callback.

You could use Ecto.Multi to implement behaviours like acts_as_list, acts_as_nested_set, counter_cache etc.


#3

Thanks for the feedback @kokolegorille, but I don’t want to implement callbacks in Phoenix, I want to embrace it’s decision to not use callbacks.

I search for callbacks while trying to enforce the user creation to always have an API token. I decided to put this enforcement in the changeset function. I would like to hear from more experienced developers if this is a good place or if there is a better one?

Best regards!


#4

You can also create separate changeset functions for each action (create, update, activate, etc).


#5

Example:

:warning: untested code

defmodule User do
  use Ecto.Schema

  import Ecto.Changeset

  @create_required [:email, :api_token]
  @update_required [:email]
  @optional []

  @type t() :: %__MODULE__{
          email: String.t(),
          api_token: String.t()
        }

  schema "users" do
    field(:email, :string)
    field(:api_token, :string)
  end

  @spec create_changeset(t(), map()) :: Ecto.Changeset.t()
  def create_changeset(struct, params) do
    struct
    |> cast(params, @create_required ++ @optional)
    |> put_api_token()
    |> validate_required(@create_required)
  end

  @spec update_changeset(t(), map()) :: Ecto.Changeset.t()
  def update_changeset(struct, params) do
    struct
    |> cast(params, @update_required ++ @optional)
    |> validate_required(@update_required)
  end

  @spec put_api_token(Ecto.Changeset.t()) :: Ecto.Changeset.t()
  defp put_api_token(%{valid?: true, data: %{api_token: nil}} = changeset) do
    api_token = :crypto.strong_rand_bytes(32) |> Base.encode64() |> binary_part(0, 32)
    put_change(changeset, :api_token, api_token)
  end

  defp put_api_token(changeset), do: changeset
end