Multiple file upload and passing entries to changeset

I’m using Live Upload with max_entries set to 5 and I’d like to store each image name inside the database. I can do this just fine with one image:

defp put_photo_urls(socket, photo) do 
    uploaded_photo =
      consume_uploaded_entries(socket, :photo, fn %{path: path}, entry ->
        photo_name = "#{entry.uuid}.#{ext(entry)}"
        # entry.client_name will give you original name of file
        dest =
          Path.join(Application.app_dir(:live_welp, "priv/static/uploads"), photo_name)

        # You will need to create `priv/static/uploads` for `File.cp!/2` to work.
        # File.cp!(path, dest)

        {:ok, photo_name}
      end)
    %{
     photo
    | "photo_url" => List.first(uploaded_photo)
     }
  end 

def handle_event("save", %{"photo" => params}, socket) do
    params = put_photo_urls(socket, params)
    save_photo(socket, params)
end

defp save_photo(socket, params) do
    IO.inspect(params)

    case Listing.create_photo(params) do
      {:ok, _photo} ->
        {:noreply,
         socket
         |> put_flash(:info, "Photo saved")
         |> redirect(to: ~p"/biz")}

      {:error, %Ecto.Changeset{} = changeset} ->
        {:noreply, assign(socket, form: to_form(changeset, as: "photo"))}
    end
  end

But if I allow for multiple image uploads I only get one photo_url. I’ve tried to store all the image URLs in a List in put_photo_urls method and then Enum.map over that List, something like this:

def handle_event("save", %{"photo" => params}, socket) do
   photo_list = put_photo_urls(socket, params)
   Enum.map(photo_list, fn url -> %{params | "photo_url" => url } end)
 end

But that isn’t working, any idea how I can do this?

I made some changes to my handle_event/3 method and now I’m able to save the photo_urls whether it’s one or multiple pics:

def handle_event(“save”, %{“photo” => params}, socket) do
photo_urls =

uploaded_photo =
  consume_uploaded_entries(socket, :photo, fn %{path: path}, entry ->
    photo_name = "#{entry.uuid}.#{ext(entry)}"

    dest =
      Path.join(Application.app_dir(:live_welp, "priv/static/uploads"), photo_name)

    File.cp!(path, dest)
    {:ok, photo_name}
  end)

for pic <- List.flatten([uploaded_photo | photo_urls]) do
  save_photo(socket, %{params | "photo_url" => pic})
end

{:noreply, socket}

end

One small problem is that when save_photo/2 is called I don’t see the flash message and it doesn’t redirect:

defp save_photo(socket, params) do
    case Listing.create_photo(params) do
      {:ok, _photo} ->
        {:noreply,
         socket
         |> put_flash(:info, "Photo saved")
         |> redirect(to: ~p"/biz")}

      {:error, %Ecto.Changeset{} = changeset} ->
        {:noreply, assign(socket, form: to_form(changeset, as: "photo"))}
    end
  end

First you are returning {:noreply, socket} from your “save” handle_event, but that socket is never updated.
Secondly you do return an updated socket from calling save_photo/2 inside your for-comprehension, but that result is lost, and more the comprehension would return a list of updated sockets. Still not what you want.

You will have to reduce over that list to get a single return value and based on that update your socket.

Two other remarks:

  • I know the documentation uses the priv folder to store the uploads, but please do not do this. Store your files outside of your application tree.

  • What do you want to happen if one of the uploaded files could not be saved to the database?