I have followed Chris’s tutorial to do file uploads with LiveView, working great but he only explores the case of creating immutable media.
Ideal scenario: Editing the form and selecting a new image should replace the image, allow me to delete the old image on success. It doesn’t seem possible with this setup.
photo entry is not part of the changeset because we manipulate it manually.
I can’t check if old category.photo != newcategory.photo
at any stage.
Photo will upload but category.photo
won"t change because it isn’t part of the changeset.
defp save_category(socket, :edit, category_params) do
category = put_photo_url(socket, socket.assigns.category)
case Recipes.update_category(category, category_params, &consume_photos(socket, &1)) do
def update_category(%Category{} = category, attrs, after_save \\ &{:ok, &1}) do
IO.inspect(category)
category
|> Category.changeset(attrs)
|> Repo.update()
|> after_save(after_save)
|> IO.inspect()
end
defp consume_photos(socket, %Category{} = category) do
consume_uploaded_entries(socket, :photo, fn meta, entry ->
I have been without proper sleep for a while, am I missing something?
How do you guys handle this?
I came up with a hacky workaround like this but for some reason, photo
field will not update in the database. losing my marbles over here! It is included and reflected in the changeset.
defp save_category(socket, :edit, category_params) do
old_photo = socket.assigns.category.photo
category = put_photo_url(socket, socket.assigns.category)
# This should only change if there is ever an upload!
if category.photo && old_photo != category.photo do
IO.inspect("I HIT HERE")
IO.inspect(old_photo)
category_params = category_params |> Map.put_new("photo", category.photo)
IO.inspect(category_params)
end
case Recipes.update_category(category, category_params) do
{:ok, newcategory} ->
IO.inspect(newcategory)
consume_photos(socket, newcategory)
remove_photos(old_photo)
def update_category(%Category{} = category, attrs) do
category
|> Category.changeset(attrs)
|> Repo.update()
end
field is definitely included in the changeset
def changeset(category, attrs) do
category
|> cast(attrs, [:order, :name, :photo])
|> validate_required([:order, :name])
end
you may want to move this outside your if
:
category_params =
if category.photo && old_photo != category.photo do
IO.inspect("I HIT HERE")
IO.inspect(old_photo)
category_params |> Map.put_new("photo", category.photo)
IO.inspect(category_params)
end
This has to do with scoping rules. Basically, the category_params
in your case
call is the exact same one you got as the 3rd parameter. The category_params
inside the if
is in the if
’s scope, not the function’s scope, thus the re-binding inside the if’s scope had no effect outside.
https://elixir-lang.readthedocs.io/en/latest/technical/scoping.html
Try this in the console:
iex> var = "this is var"
iex> if true, do: var = "this is another var"
iex> IO.inspect(var)
iex> "this is var"
1 Like
thank you but didn’t make any difference
What’s happening in put_photo_url
? If you are manually setting the photo
field there, then when you pass the struct to the changeset, it won’t see it as a change.
Only the fields present in the changes
part of the changeset are sent to the database. Any other, in-memory changes done to the schema are ignored.
Ecto.Repo — Ecto v3.11.1
1 Like
Thank you, lifesaver didn’t spot that obvious thing!
The whole tutorial felt a bit hacky so in the end.I followed ecto way I think a better path without dirtying the context too.
Now I can update the picture if selected and delete the old one as necessary in a tidier manner.
defp save_category(socket, :edit, category_params) do
category_params = put_photo_url(socket, category_params)
case Recipes.update_category(socket.assigns.category, category_params) do
{:ok, new_category} ->
consume_photos(socket)
if socket.assigns.category.photo != new_category.photo do
remove_photos(socket.assigns.category.photo)
end
defp save_category(socket, :new, category_params) do
category_params = put_photo_url(socket, category_params)
case Recipes.create_category(category_params) do
{:ok, _category} ->
consume_photos(socket)
defp put_photo_url(socket, params) do
...
Map.put_new(params, "photo", List.first(urls))
end
The reset is reverted to the previous state before the tutorial
2 Likes