Current section
Files
Jump to
Current section
Files
lib/uploader.ex
defmodule Uploader do
@callback maybe_copy_file(
tmp_file_path :: String.t(),
new_file_path :: String.t(),
on_file_exists :: atom | nil
) :: :ok | {:error, term}
@doc ~S"""
Store all the uploaded files.
This function is expected to be called inside a transaction:
Multi.new()
|> Multi.insert(:user, user_changeset)
|> Multi.run(:upload_files, fn _repo, %{user: user} ->
Uploader.store_files(user)
end)
|> Repo.transaction()
|> case do
{:ok, %{user: user}} ->
{:ok, user}
{:error, :user, %Ecto.Changeset{} = changeset, _changes} ->
{:error, changeset}
{:error, :upload_files, {:file_path_exists, file_path}, _changes} ->
raise "file upload failed: path \"#{file_path}\" already exists"
end
This function fetches all the uploadable schema fields (with all of their options)
from the given Schema struct (see `Uploader.Ecto.UploadableFields`), and for each
field calls the `maybe_copy_file/4` callback function. This callback function is
called from the module defined in the `:uploader` option of the field (by default,
the module `Uploader.LocalUploader` is used). Below is an example on how to define
a custom module implementing the `maybe_copy_file/4` callback function in the Schema:
schema "users" do
uploadable_field :avatar_image, uploader: AmazonS3Uploader
# other fields
end
In case of success `{:ok, nil}` is returned, otherwise `{:error, error_tuple}`
is returned with `error_tuple` being a two-elements tuple describing the error.
This tuple is in fact the returned error value of the call to the
`maybe_copy_file/4` function. An example of a returned error value is
`{:error, {:file_path_exists, file_path}}`.
"""
@spec store_files(struct) :: {:ok, nil} | {:error, term}
def store_files(%{__struct__: _struct_name} = entity) do
uploadable_fields_with_opts = entity.__struct__.get_uploadable_fields()
try do
for {uploadable_field, opts} <- uploadable_fields_with_opts,
field_contains_new_uploaded_file?(entity, uploadable_field) do
uploader = Keyword.get(opts, :uploader, Uploader.LocalUploader)
on_file_exists = Keyword.get(opts, :on_file_exists)
directory = Keyword.get(opts, :directory, "")
filenames =
Keyword.get(opts, :filename, &filename/2).(entity, uploadable_field)
|> List.wrap()
%Plug.Upload{path: tmp_file_path} =
Map.fetch!(entity, String.to_atom("uploaded_" <> Atom.to_string(uploadable_field)))
for filename <- filenames do
new_file_path = Path.join(directory, filename)
status_copy_file =
apply(uploader, :maybe_copy_file, [
tmp_file_path,
new_file_path,
on_file_exists
])
case status_copy_file do
:ok -> :ok
{:error, _} = error -> throw({:break, error})
end
end
end
{:ok, nil}
catch
{:break, error} -> error
end
end
defp field_contains_new_uploaded_file?(entity, uploadable_field) do
Map.fetch!(entity, uploadable_field) &&
Map.fetch!(entity, String.to_atom("uploaded_" <> Atom.to_string(uploadable_field)))
end
defp filename(%{__struct__: _struct_name} = entity, field_name) do
Map.fetch!(entity, field_name)
end
end