Nested relation not updated on Repo.Update()

Hey guys, I’m currently struggling with Ecto and its Repo.update() method.

  • I have a struct Post and a struct Author.
  • The relation is Post belongs_to Authors and contains the foreign key user_id.
  • The Author has been preloaded.

My goal is now to change the user_id to a different user and return the updated Post. I am using the default scaffolding method for the update:

  def update_post(%Post{} = post, attrs) do
    |> Post.changeset(attrs)
    |> Repo.update()

I call it with the attrs %{user_id: 123}. If I call the update method, I correctly get back a tuple {:ok, post}, but the preloaded Author in this post, has not been updated. So the result is like this:

  title: "...",
  content: "...", 
  user_id: 123, # <-- this is the updated user_id
  author: %{
    id: *567*, # <-- this is still the old user_id
    name: "old_user",

Look at the Ids, they are not what I want.

How can I make sure, that the updated user is also returned? I’m not sure if I have to preload again or something else. The only dirty workaround I came up with, is fetching the Post and preload the Author again.

1 Like

You might use read_after_write…

From docs.

schema(:read_after_writes) - Non-virtual fields that must be read back from the database after every write (insert or update);

1 Like

How would I use it? I’d like to fetch the author again, but that fied is defined using belongs_to, which does not accept the :read_after_writes option.

This code worked for me:

def update_post(%Post{} = post, attrs) do
  |> Post.changeset(attrs)
  |> Repo.update!()
  |> Repo.preload(:author, force: true)

I used update! for easy piping, but could be done with update.

I found this issue where a solution using :read_after_writes is mentioned, but I could not get that to work.

Thank you. Seems I wasn’t able to find the mentioned issue. I decided to stick with explicit, forced preloading. :slight_smile: