Open mayel opened 3 years ago
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:
waffle_ecto
so I've implemented my own logic on top of a simple string
ecto field.The rest was quite straightforward to implement :)
@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.
@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.
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
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
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
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.
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
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