2015-03-28 18 views
17

Próbuję wstawić strukturę faktury wraz z powiązanymi elementami faktury. Mogę wstawić dane do faktury i zadzwonić do anonimowej funkcji, aby zweryfikować, odlać i wstawić każdy przedmiot. Ponieważ insert/2 nie generuje zwrotu, w jaki sposób mogę uzyskać wartość invoice_id dla produktów, gdy wciąż można wycofać całą transakcję, jeśli jeden element nie przejdzie sprawdzania lub wstawiania?Wstawianie powiązanych modeli w Ecto

Mam umieścić kod w moim repo, to jest tutaj:

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 

i tutaj jest, jak go używać z mojego kontrolera:

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 

Nie ma wiele do góry do tej pory przykłady z Ecto, więc przepraszam, jeśli to są pytania noob ;-). Ktoś ma pomysł? Próbowałem wstawić fakturę wstawić do prywatnej funkcji i użyć bloku przypadku, aby ustalić, czy główna transakcja powinna zostać wycofana, ale nie mogłem również wymyślić, jak odzyskać z tego identyfikator faktury.

Odpowiedz

27

Repo.insert/1 faktycznie zwraca właśnie wstawiony model. Również chcesz możliwie jak najbardziej oddzielić sprawdzanie poprawności od obsługi transakcji. Proponuję coś w następujący sposób:

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

Dziękuję, to jest o wiele bardziej eleganckie. BTW, dla tych, którzy będą z tego korzystać, oto jak poradziłem sobie z błędami, aby je zakodować w JSON: 'errors = Enum.map (items, & (& 1.errors)) |> Enum.into ([invoice.errors ]) |> Enum.map (& (Enum.into (& 1,% {}))) {: errors, errors} ' –

+2

Interfejs API zmienił się teraz, aby' Repo.insert' zastąpić 'Repo. wstaw! ', aby zwrócić model lub zgłosić błąd w transakcji. – Arrel

+3

Co więcej, Ecto obsługuje teraz również 'put_assoc', który pozwala na zmianę schematu nadrzędnego wraz z jego dziećmi, co znacznie uprościłoby powyższy kod! –

6

W Ecto 2,0 byś robił coś takiego:

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

(Przyjęty odpowiedź działa pre 2.0, również Valim wspomina w komentarzach tej odpowiedzi z istnienie put_assoc)