LiveView Image Upload - Update does not work

I’m getting started with LiveView Upload by following Chris McCord’s Video ( https://youtu.be/PffpT2eslH8?t=795 ) and I have some trouble with updating an entity with a new Image.

In the Video, in the :edit Action of the LiveView, he’s doing it like this:

post = put_photo_urls(socket, socket.assigns.post)
Timeline.update_post(post, post_params) 

The update_post function in the context looks like this:

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

In the Schema he adds the :photo_urls field, but does not add it to the changeset function and says “we’ll set it directly”.

The Problem is, if I try this in my own app with a very similar structure (I just have one image_url instead of multiple photo_urls and my model is called Character with some other fields as well), the image_url does not get updated…

If I try by hand and change the image_url of an existing Character

c = Characters.get_character!(1)  |> Map.put(:image_url, "some_image_url")

And then pipe it through the Changeset with an empty dictionary (note, just like in the Video, my changeset function does not include image_url)

c |> Character.changeset(%{})

I get an empty changeset back, and then piping the result into Repo.update() does not cause a Database Query.

Interestingly though, the return value of Repo.update is the character WITH image_url set…

On the other hand, it works when creating a new character and that uses the same approach (basically %Character{image_url: "some_url"} |> Character.changeset(attrs) |> Repo.insert() )

1 Like

Welcome @iv-mexx!

What happens if you put the new image URL into the changeset instead of into the struct? You can do so with Ecto.Changeset.put_change/3:

c
|> Character.changeset(%{})
|> Ecto.Changeset.put_change(:image_url, "new_image_url")
1 Like

Thanks for your quick reply!

I can kind of get it to work, but in the whole context only ugly and around many corners, and then it would certainly be better to put it into the attrs by hand.

So in the Video, Chris creates a function in the LiveView (I’m again showing my code thats basically copied but with my models)

  defp put_image_url(socket, %Character{} = character) do
    case uploaded_entries(socket, :image) do
    {[], _} ->
      # Image is optional
      character
    {completed, []} ->
      # Character only has 1 image, so only take the first
      [url | _] = for entry <- completed do
        Routes.static_path(socket, "/uploads/#{entry.uuid}.#{ext(entry)}")
      end

      %Character{character | image_url:  url}

    end
  end

And uses that one in both, create and edit:

  defp save_character(socket, :new, character_params) do
    character = put_image_url(socket, %Character{})

    case Characters.create_character(character, character_params, &consume_image(socket, &1)) do
    ...
  defp save_character(socket, :edit, character_params) do
    character = put_image_url(socket, socket.assigns.character)

    case Characters.update_character(character, character_params, &consume_image(socket, &1)) do
    ...

If I do that and use put_change/3 in the Context, it does not change anything. I guess, because the character already has the same image_url set, so it is not a change.

But thats where I can’t wrap my head around, because thats exactly what Chris is doing in the Video…
To be fair, he did not demo editing, so maybe it would not work in his code as well?