How to chain controller actions or DB inserts in Phoenix?

Sorry for the terrible title. I’m having a case of the “you don’t know what you don’t know” and am unsure if there is a way to get this done that I haven’t been able to find. Basically what I want to do is:

When calling the create action of my UserController, I want to trigger the create action in my TodoController to insert 3 new Todos in the database. So in other words, once a new user is created, I’d like to create “starting” data for them without first doing a check on the front end (I’m using React, if that matters) and then doing a POST after running client-side calculations. Maybe that’s the wrong terminology, or the wrong way to go about thinking that, but I’m pretty new to elixir and phoenix, and functional programming, so having your experienced insight will help me get things to click!

I hope this makes sense. Any help would be much appreciated!

1 Like

I made a post a few days ago that I think answers your question:

You could also try out put_assoc or cast_assoc maybe:
https://medium.com/coryodaniel/til-elixir-ecto-put-assoc-vs-cast-assoc-7c80f35f6e6

Unless I’m misunderstanding something I think this is exactly what Phoenix is not your application is all about. If the functionality for creating Todos wasn’t buried in the TodoController it would be much easier for UserController to create Todos.

Also

3 Likes

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