How to chain controller actions or DB inserts in Phoenix?

As @peerreynders hinted, the right way to think about this is not how to invoke some other controller action to complete user registration, but to place the concerns of what all should happen when creating a user inside of a well named module and function. Check out our Context guide for more details around this idea, but for your specific case, let’s imagine your user/session controller called into something like this:

defmodule MyAppWeb.SessionController do
  def create(conn, %{"user" => user_params}) do
    case Accounts.register_user(user_params) do
      {:ok, user} ->
        conn
        |> put_flash(:info, "Welcome aboard!")
        |> signin_user(user)
        |> redirect(to: user_todo_path(conn, :index, user)

      {:error, changeset} -> render("new.html", changeset: changeset)
    end
  end
end

With something like Accounts.register_user(...) our phoenix code doesn’t need to know or care about what all should happen when a user signs up. We can place all the concerns of this inside a function that accomplishes everything that should happen. Now later if we want to send welcome emails, we have a perfect spot for it. Likewise, if we want to offer registration over crud forms, as well as react consuming a JSON API, as well as react consuming a GraphQL Absinthe schema, all web endpoints simply call into the same function. Diving deeper, our Accounts and Todos contexts could look something like this:


defmodule MyApp.Accounts do
  alias MyApp.Accounts.User
  alias MyApp.Repo
  alias Ecto.Multi

  def register_user(params) do
    Multi.new()
    |> Multi.insert(:user, User.changeset(%User{}, params))
    |> Multi.run(:seed, fn %{user: user} -> Todos.seed_starting_todos(user) end)
    |> Repo.transaction()
  end
end

defmodule MyApp.Todos do
  alias MyApp.Repo
  alias MyApp.Todos.Todo

  def seed_starting_todos(user) do
    Repo.insert_all(Todo, [
      Ecto.build_assoc(user, :todos, title: "My first todo"),
      Ecto.build_assoc(user, :todos, title: "My second todo"),
      Ecto.build_assoc(user, :todos, title: "My third todo"),
    ])
  end

  def create_todo(user, params) do
    ...
  end 
end

Now instead of jumping through hoops to try to chain different controller actions or have the client be concerned with what should be POST’d to once a user is registered, we have a natural place to handle it. Likewise, when we build out the Todo controller, we can define function for managing todos and friends inside a module that wraps up all the shared todo functionality. Make sense?

13 Likes