Issues with uploading more than one picture in a form

Hi guys i have this form that has 2 file_input. one for photo another for signature. but am finding it difficult to write my logic to upload the 2 field at a time. the logic i have works for photo works well. please i need your help .
here is my code

def create(conn, %{"identification_document" => identification_document_params}) do
    if upload = (identification_document_params["photo"])  do
      fpth = upload_file(upload)
      identification_document_params =
        Map.merge(identification_document_params, %{"photo" => fpth})
      case ScholarshipFormTwo.create_identification_document(identification_document_params) do
      {:ok, identification_document} ->
        conn
        |> put_flash(:info, "Identification document created successfully.")
        |> redirect(to: Routes.identification_document_path(conn, :show, identification_document))

      {:error, %Ecto.Changeset{} = changeset} ->
        delete_old_file(fpth)
        changeset |> IO.inspect()
        Enum.into(identification_document_params, %{"photo" => nil})

        render(conn, "new.html", changeset: changeset)

      end
     end
    end

What error are you having? Each should come in their own field just fine and thus you should be able to handle each individually.

1 Like

thanks. i just did’nt know how to handle the the two at a time pls i need help

for that there was no error. i just want to be able to upload that of the signature along side the photo.

First, I’m not entirely sure that it would be a good idea to wrap your entire action in that if upload..., because if it is indeed a required parameter, it would be much better to let ecto validate it and return some useful error to the user.

As to how you would upload multiple files, observe that the code from the if statement to the case statement ‘takes’ a map and returns a map, so you could simply chain a similar code and get the desired output, or simply repeat it eg :

identifiaction_params = case identification_params["photo"] do
  nil -> identification_params
 upload -> fpth = upload_file(upload)
                 identification_params |> Map.merge(%{"photo" => fpth})
# and then the same thing for "signature"

You could wrap this logic in a function that takes a map and a string and returns a map and then simply pipe the params map through a chain of functions.

Note that there are some issues that are not handled here : what happens if upload_file fails ?

2 Likes

i appreciate your response but i really don’t understand you. could please elaborate. am not so good with elixir.

def create(conn, %{"identification_document" => identification_params}) do
identifiaction_params = case identification_params["photo"] do
  nil -> identification_params
 upload -> fpth = upload_file(upload)
                 identification_params |> Map.merge(%{"photo" => fpth})
   end
identifiaction_params = case identification_params["signature"] do
  nil -> identification_params
 upload -> fpth = upload_file(upload)
                 identification_params |> Map.merge(%{"photo" => fpth})
    end
 case ScholarshipFormTwo.create_identification_document(identification_document_params) do
      {:ok, identification_document} ->
        conn
        |> put_flash(:info, "Identification document created successfully.")
        |> redirect(to: Routes.identification_document_path(conn, :show, identification_document))

      {:error, %Ecto.Changeset{} = changeset} ->
        delete_old_file(fpth)
        changeset |> IO.inspect()
        Enum.into(identification_document_params, %{"photo" => nil})

        render(conn, "new.html", changeset: changeset)

    
     end
    end

or slightly better :

def create(conn, %{"identification_document" => identification_params}) do
identifiaction_params = identification_params 
                                    |> process_upload("photo") 
                                    |> process_upload("signature")
 case ScholarshipFormTwo.create_identification_document(identification_params) do
      {:ok, identification_document} ->
        conn
        |> put_flash(:info, "Identification document created successfully.")
        |> redirect(to: Routes.identification_document_path(conn, :show, identification_document))

      {:error, %Ecto.Changeset{} = changeset} ->
        delete_old_file(fpth)
        changeset |> IO.inspect()
        Enum.into(identification_params, %{"photo" => nil})

        render(conn, "new.html", changeset: changeset)

    
     end
    end

defp process_upload(params, field) do
 case params[field] do
  nil -> params
 upload -> fpth = upload_file(upload)
                 params |> Map.merge(%{field => fpth})
end
end

But neither solution are really good in that they don’t account for the fact that things could go wrong with upload_file

2 Likes

ok i really do appreciate you. let me try it out. but can i chat you privately? i like you to mentor me if you don’t mind.
thanks

hi Krstfk. i got the following errors

malformed request, a UndefinedFunctionError exception was raised with message “function :raw_file_io_delayed.open_layer/3 is undefined (module :raw_file_io_delayed is not available)”

This is probably because I forgot to close the case blocks with an end (I edited the relevant lines in my answer).

1 Like

i did closed it sir at my own end here

Could you perhaps post a more complete log of the error. Also, what versions of elixir erlang otp are you running ?

1 Like

Erlang/OTP 22 [erts-10.4.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe]
Elixir 1.9.0 (compiled with Erlang/OTP 21).

looks like i got it fixed. well i don’t know if its the best way. i just did nested if (if condition inside another if).
thanks again Krstfk. although a better solution will not be taken for granted.