One form, multiple models stored in database

I’m trying to build a signup form which is able to create multiple models. Not only needs it to create a User but also creates an Organization.

I’m trying to find the best way to handle this. I’m following some of the advice from this talk: Controller Control: Designing Domains for Web Applications - Gary Rennie, and this post: Beyond 10,000 Lines by keeping my controllers Repo free and moving most of the logic to services. My goals are to place as much of the logic in services which can be reused in other places as well. Next to that I’m also keeping my schema’s really small, and only describe the tables in the database. This in preparation already for the next version of Phoenix, which I have understood is going in that direction.

Now I have two base services already in place, one for creating a User and one for creating an Organization.
To accomplish what I want with this page I have created a separate service that uses Ecto.Multi to call the other two services like this

    defmodule Erry.Signups.Signup do
      def create(email, password, organization) do
        Ecto.Multi.new()
        |> Ecto.Multi.run(:user, fn _ ->
          Erry.Users.Register.call(email, password)
        end)
        |> Ecto.Multi.run(:organization, fn %{user: user} ->
          Erry.Organizations.Builder.call(organization, user)
        end)
        |> Erry.Repo.transaction
        |> handle_new_signup
      end

      defp handle_new_signup({:ok, results}) do
        user = results.user
        {:ok, user}
      end

      defp handle_new_signup({:error, _, changeset, _}) do
        {:error, changeset}
      end
    end

And from my controller I call

    case Erry.Signups.Signup.create(email, password, organization) do
      {:ok, _user} -> redirect(conn, home_path(conn, :index))
      {:error, changeset} -> render(conn, "new.html", changeset: changeset)
    end

Now each service uses different schema’s which can have validations which can fail. But when using Eco.Multi I will only get the changeset of the failing call. This changeset though has not all the information given by the user using the form, so it can’t be used again to populate the form again with errors being shown. I have thought about adding a separate schema for the signup flow like this:

defmodule Erry.Signup do
  use Erry.Web, :model

  embedded_schema do
     field :email, :string
     field :password, :string
     field :organization_name, :string
  end

  def changeset(struct, params \\ %{}) do
    struct
    |> cast(params, [:email, :password, :organization_name])
  end
end

But I’m not really sure if that is the best way to go or how to integrate it with the rest.

Regards,
Marcel

1 Like

I think you’re talking about a similar problem to what I was asking about last night:

…Paul

2 Likes

Btw, we just went through an example in the mailing list too: https://groups.google.com/forum/#!topic/elixir-ecto/_QHS-IY-sQY

Please write about such examples (articles, blogs, docs, etc) if you find they are useful so it helps people doing similar in the future.

8 Likes