2015-03-28 8 views
17

Ich versuche, eine Rechnungsstruktur zusammen mit den zugehörigen Rechnungspositionen einzufügen. Ich kann die Rechnungsdaten einfügen und eine anonyme Funktion aufrufen, um jedes Element zu validieren, zu konvertieren und einzufügen. Da insert/2 keine Rückgabe liefert, wie kann ich die invoice_id für die Elemente abrufen, während ich immer noch die gesamte Transaktion zurücksetzen kann, wenn ein Element die Validierung oder Einfügung nicht besteht?Einfügen verknüpfter Modelle in Ecto

ich den Code in meinem eigenen Repo gesetzt haben, hier ist es:

def insertassoc(params) do 
Repo.transaction(fn ->  
    i = Invoice.changeset(params["data"], :create) 
    if i.valid? do 
     Repo.insert(i) 
    else 
     Repo.rollback(i.errors) 
    end 

    insert_include = fn k -> 
    c = InvoiceItem.changeset(k, :create) 
    if c.valid? do 
     Repo.insert(c) 
    else 
     Repo.rollback(c.errors) 
    end 
    end 

    for include <- params["includes"] do 
    insert_include.(Map.merge(include, %{"invoice_id" => ????})) 
    end 

end) 
end 

und hier ist, wie ich es von meinem Controller verwenden:

def create(conn, params) do 
case InvoiceRepo.insertassoc(params) do 
    {:ok, x} -> 
    json conn, Map.merge(params, %{"message" => "OK"}) 
    {:error, x} -> 
    json conn |> put_status(400), Map.merge(params, %{"message" 
    => "Error"}) 
end 
end 

Es sind nicht viele up um da draußen Beispiele mit Ecto zu geben, es tut mir leid, wenn das Noob-Fragen sind ;-). Jeder hat eine Idee? Ich habe versucht, die Rechnungsbeilage in eine private Funktion zu setzen und einen Fallblock zu verwenden, um festzustellen, ob die Haupttransaktion zurückgesetzt werden sollte, aber ich konnte auch nicht herausfinden, wie ich die Rechnungs-ID zurückbekomme.

Antwort

27

Repo.insert/1 gibt das Modell zurück, das Sie gerade eingefügt haben. Außerdem möchten Sie die Validierung so weit wie möglich von der Transaktionsverarbeitung entkoppeln. Ich würde etwas vorschlagen, wie folgt:

invoice = Invoice.changeset(params["data"], :create) 
items = Enum.map(params["includes"], &InvoiceItem.changeset(&1, :create)) 

if invoice.valid? && Enum.all?(items, & &1.valid?) do 
    Repo.transaction fn -> 
    invoice = Repo.insert(invoice) 
    Enum.map(items, fn item -> 
     item = Ecto.Changeset.change(item, invoice_id: invoice.id) 
     Repo.insert(item) 
    end) 
    end 
else 
    # handle errors 
end 
+0

Vielen Dank, das ist viel eleganter. BTW, für diejenigen, die das verwenden werden, hier ist, wie ich die Fehler behandelt habe, damit sie in JSON codiert werden: 'errors = Enum.map (items, & (& 1.errors)) |> Enum.into ([invoice.errors ]) |> Enum.map (& (Enum.into (& 1,% {}))) {: errors, errors} ' –

+2

Die API hat sich jetzt geändert, so dass' Repo.insert' durch 'Repo' ersetzt werden sollte. Einfügen! ', um das Modell zurückzugeben oder einen Fehler innerhalb der Transaktion zu erzeugen. – Arrel

+3

Darüber hinaus unterstützt Ecto jetzt auch 'put_assoc', mit dem Sie das Elternschema neben seinen Kindern ändern können, und das würde den obigen Code viel sauberer machen! –

6

In Ecto 2.0 Sie möchten etwas tun:

%My.Invoice{} 
|> Ecto.Changeset.change 
|> Ecto.Changeset.put_assoc(:invoice_items, [My.InvoiceItem.changeset(%My.InvoiceItem{}, %{description: "bleh"})]) 
|> My.Repo.insert! 

(Die akzeptierte Antwort arbeitet Pre 2.0, auch, Valim erwähnt in den Kommentaren dieser Antwort von die Existenz von put_assoc)