Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ecto Reload belongs_to Association After Successful Repo.update

Changing the association of a given child from parent_a to parent_b via parent_id in params leaves a stale record.parent object.

e.g. (assume params matches %{child: %{id: '1', parent_id: '6'}})

# ...
child = Repo.get(Child, child_id)
|> preload([:parent])
changeset = Child.changeset(child, child_params)

case Repo.update(changeset) do
  {:ok, child} ->
    IO.puts child.parent_id # returns '6', or the new, changed `id`
    IO.puts child.parent.id # returns '5', or the old id
                            # child.parent is stale
# ...

What is the proper way to retrieve the newly associated parent record after updating?

like image 908
baoist Avatar asked Jan 22 '16 11:01

baoist


1 Answers

Force the preload. By default, Ecto won’t preload associations that are already loaded.

child
|> Child.changeset(params)
|> Repo.update!()
|> Repo.preload(:parent, force: true)

or without a bang update if you want to handle errors differently

child
|> Child.changeset(params)
|> Repo.update()
|> case do
  {:ok, child} -> {:ok, Repo.preload(child, :parent, force: true)}
  error -> error
end

In a more realistic example with error handling, it could look something like

with {:ok, child} <- get_child(child_id),
     {:ok, child} <- update_child(child, params) do
  # Do stuff
else
  {:error, %Ecto.Changeset{} = changeset} -> # Handle error
  {:error, reason} -> # Handle error
end

defp get_child(child_id) do
  case Repo.get(Child, child_id) do
    nil -> {:error, :not_found}
    child -> {:ok, child}
  end  
end

defp update_child(child, params) do
  updated_child = 
    child
    |> Child.changeset(params)
    |> Repo.update!()
    |> Repo.preload(:parent, force: true)
rescue
  error in Ecto.InvalidChangesetError -> {:error, error.changeset}
  error in RuntimeError -> {:error, error.message}
end
like image 165
Dennis Avatar answered Sep 19 '22 11:09

Dennis