Best way to handle associations in single html form

Hi everyone,

I have been learning a bit more on Ecto and Phoenix. Specifically in how to handle a master/detail o parent/child associations and the corresponding Web Form.

My question: Is there a better way to handle this typical scenario than the one I implemented below?

In my journey I read about:

  1. Ecto associations, multi, cast_assoc, put_assoc, embeded_schemas, transactions
  2. Drab Library

Checktout many resources like:

  1. Phoenix, Ecto, Drab official documentation
  2. Different Forums: Medium, Elixir Forum
  3. What’s new in Ecto 2.0
  4. Others

Adding them up and based on my understanding of some topics I came out with the following:
Scenario
Phoenix: 1.3
Elixir: 1.6.1
Ecto: 2.2.8
Schema: Invoices --> has_many --> details, belongs_to
Context name: Documents
I used basic generator to generate both

This I changed:

Invoice controller: action new

  def new(conn, _params) do
    changeset = Documents.change_invoice(
      %Invoice{
        details: [%Multipartform.Documents.Detail{}]
      }
    )
    render(conn, "new.html", changeset: changeset)
  end

Documents api:

  def add_records(%{invoice: invoice}, attrs) do
    records = 
      Enum.map(attrs["details"], fn {_, d} ->
        Map.put(d, "invoice_id", invoice.id)
      end)
      |> Enum.map(&Multipartform.Documents.Detail.changeset(
           %Multipartform.Documents.Detail{},&1)
      ) 
      |> Enum.map(fn ch -> {:ok, _d} = Repo.insert(ch) end)
    {:ok, records}
  end

  def save_invoice(invoice_changeset, attrs) do
    Multi.new()
    |> Multi.insert(:invoice, invoice_changeset)
    |> Multi.run(:add_detail, &add_records(&1, attrs))
  end

  def create_invoice(attrs \\ %{}) do
    invoice_changeset =
      %Invoice{}
      |> Invoice.changeset(attrs)

    case Repo.transaction(save_invoice(invoice_changeset, attrs)) do
      {:ok, %{invoice: invoice, add_detail: _details}} ->
        {:ok, invoice}
      {:error, _failed_operation, _failed_value, _changes_so_far} ->
        {:error, invoice_changeset}
    end 
  end

The view part is very straight forward and simplified using Drab to add more details in the UI.

Even when the Ecto documentation seemed very clear I was not able to figure out ecto cast_assoc, put_assoc and how they work together. Another challenge was understanding ecto Multi.

Can’t wait to see the @darinwilson Ecto Book.

Thanks for your comments and suggestions.

9 Likes

I’m describing how I’m using an Ecto.Multi to insert parent/children entities with associations in this answer:

1 Like