Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LiveView support #71

Open
mayel opened this issue Dec 3, 2020 · 8 comments
Open

LiveView support #71

mayel opened this issue Dec 3, 2020 · 8 comments

Comments

@mayel
Copy link

mayel commented Dec 3, 2020

With the new upload functionality bundled in Phoenix LiveView, I wonder if anyone has successfully used Waffle with it, and if there are any gotchas or changes needed in the lib?

Info about LiveView uploads: https://hexdocs.pm/phoenix_live_view/uploads.html#content and https://hexdocs.pm/phoenix_live_view/uploads-external.html

@achedeuzot
Copy link

I've currently used Waffle (without Waffle Ecto) with Phoenix LiveView.

I followed the phoenix blog & screencast https://www.phoenixframework.org/blog/phoenix-live-view-upload-deep-dive with a few changes:

  • I've used the Mime type validation code which is in PR implement file validation by magic bytes #69 but I now have a two step process (one for validating, one for consuming the uploads after the DB update)
  • I couldn't see how it would work with waffle_ecto so I've implemented my own logic on top of a simple string ecto field.

The rest was quite straightforward to implement :)

@achempion
Copy link
Member

@achedeuzot Could you please describe what issues did you have with waffle_ecto library? It provides couple of convenient functions like cast_attachements for the changeset as well as cache management and url generation which you still need for the LiveView.

@achedeuzot
Copy link

@achempion Indeed, cast_attachements seemed perfect for what I wanted but I couldn't see how to make it work with the consume_uploaded_entries function from LiveView if I followed the sample video from the Phoenix blog. I moved the consume_uploaded_entries before the call to the changeset to try and give the file path into cast_attachements but I couldn't make it work.

If I look at the code of waffle_ecto, I can see cast_attachements accepts a %Plug.Upload{} which I don't have (LiveViews provides a %LiveView.UploadEntry{}), a %{filename: filename, binary: binary} map which I don't have either (and I don't want to read the file into the :binary key for memory consumption reasons) or a simple file path which looks like what I want but fails with :invalid_path even if I set accept_paths: true, the file path given by consume_uploaded_entries gives something like /var/folders/e4/rnd23gsflk23_ynglbkeshdq9_m02340gq/T//plug-1704/live_view_upload-094958382-29457213941-4.

Right now my steps are the following:
1 - validate schema params through a standard changeset function
2 - validate uploaded files through the MyApp.AvatarUploader.validate/1 function and add errors into the changeset if needed
3 - save the record to database if everything is OK
4 - save the file to storage if everything is OK. If not, I'm rolling back the field in the DB.

I also couldn't see where I could call a custom validate function on the passed uploaded file using cast_attachements.

Finally, the way the MyApp.AvatarUploader module needs to contain use Waffle.Ecto.Definition and the schema must contain field: :avatar, MyApp.AvatarUploader.Type created a dependency schema which doesn't work well with my current application setup: the MyApp.AvatarUploader lives in an app that depends on the Ecto schema app but not the other way around. So it's creating a circular dependency between apps I'd rather not create.

@pierre-pretorius
Copy link

My app is completely written in LiveView. Doesn't seem like Waffle needs special support for LiveView. I followed this guide:
https://www.phoenixframework.org/blog/phoenix-live-view-upload-deep-dive

And implemented the consume method like this. Note I have max_entries set to 1 so I'm only expecting one file.

def consume_logo(socket, %Organization{} = organization) do
    consume_uploaded_entries(socket, :logo, fn meta, _entry -> {:ok, _} = Uploaders.Logo.store({meta.path, organization}) end)
    {:ok, organization}
end

@montebrown
Copy link

montebrown commented Feb 8, 2021

Hi @achedeuzot,

You can use waffle_ecto - I was able to get this working without changing my schema. You can construct a %Plug.Upload{} and pass it in to the changeset. Something like:

def handle_event("save", _params, socket) do
  consume_uploaded_entries(socket, :avatar, fn meta, entry ->
    {:ok, user} =
      Accounts.update_user(socket.assigns.current_user, %{
        "avatar" => %Plug.Upload{
          content_type: entry.client_type,
          filename: entry.client_name,
          path: meta.path
        },
      })

    Snap.Uploaders.Avatar.url({user.avatar, user}, :original)
  end)

  {:noreply, socket |> update(:uploaded_files, &(&1 ++ uploaded_files)) |> fetch_user()}
end

@achedeuzot
Copy link

Hi @montebrown !

Thanks for your solution 🤗

My main issue was to find a way to properly manage the user update transaction as well as the possible errors of the validation and your solution is spot on, thanks ! I'll just need to raise in case of an {:error, %Ecto.Changeset{}} return value to the Accounts.update_user so I can show proper error messages.

My last issue to resolve now with waffle is how to use multiple S3 buckets that each have their own access tokens :P

@TomEversdijk
Copy link

Thanks @montebrown for your solution!

For everybody who tried to get this working it is important to use the cast_attachements method within the consume_uploaded_entries. The consume_uploaded_entries will automatically delete the file-path when it is finished so otherwise and {:error, :invalid_file_path} error will be returned.

@stratigos
Copy link

Thanks to everyone in the history above that helped me find my way with handling the file uploads with today's LiveView of 0.20.15.

Given this:

For everybody who tried to get this working it is important to use the cast_attachements method within the consume_uploaded_entries. The consume_uploaded_entries will automatically delete the file-path when it is finished so otherwise and {:error, :invalid_file_path} error will be returned.

The consume_uploaded_entries function's closure now can have a return type of {:postpone, result}, which allows the tmp file to persist and be handled after consume_uploaded_entries finishes (which allows waffle to handle the changeset as normal).

https://hexdocs.pm/phoenix_live_view/0.20.14/Phoenix.LiveView.html#consume_uploaded_entries/3

I found success with the following:

# I have `max_entries: 1` set, the following expects only one `entry`:
def handle_event(
  "save",
  %{"schema" => schema_params},
  socket
) do
  [schema_params_with_image] =
    consume_uploaded_entries(socket, :image, fn meta, entry ->
      image_plug_upload =
        %Plug.Upload{
          content_type: entry.client_type,
          filename: entry.client_name,
          path: meta.path
        }

      updated_params =
        Map.put(schema_params, "image", image_plug_upload)

      {:postpone, updated_params}
    end)

  save_schema(
    socket,
    socket.assigns.action,
    schema_params_with_image
  )
end   

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants