defmodule Uploadex.Context do @moduledoc """ Context Helper functions for handling files. Note that all functions in this module require the Uploader as an argument. You are free to call them like that: iex> Uploadex.Context.create_with_file(changeset, Repo, MyUploader) {:ok, %User{}} However, by doing `use Uploadex, repo: Repo` in your uploader, you can call these functions directly through the uploader to avoid having to pass these extra arguments around: iex> MyUploader.create_with_file(changeset) {:ok, %User{}} """ alias Ecto.{ Changeset, Multi, } alias Uploadex.Uploader @doc """ Inserts the changeset and store the record files in a database transaction, so if the files fail to be stored the record will not be created. """ @spec create_with_file(Changeset.t, module, Uploader.t, keyword) :: {:ok, any} | {:error, any()} def create_with_file(changeset, repo, uploader, opts \\ []) do Multi.new() |> Multi.run(:insert, fn _repo, _ -> repo.insert(changeset, opts) end) |> Multi.run(:store_files, fn _repo, %{insert: record} -> uploader.store_files(record) end) |> repo.transaction() |> convert_result() end @doc """ Updates the record and its files in a database transaction, so if the files fail to be stored the record will not be created. This function also deletes files that are no longer referenced. """ @spec update_with_file(Changeset.t, any, module, Uploader.t, keyword) :: {:ok, any} | {:error, any()} def update_with_file(changeset, previous_record, repo, uploader, opts \\ []) do Multi.new() |> Multi.run(:update, fn _repo, _ -> repo.update(changeset, opts) end) |> Multi.run(:store_files, fn _repo, %{update: record} -> uploader.store_files(record, previous_record) end) |> Multi.run(:delete_file, fn _repo, %{update: record} -> uploader.delete_previous_files(record, previous_record) end) |> repo.transaction() |> convert_result() end @doc """ Similar to `update_with_file/3`, but does not delete previous files. """ @spec update_with_file_keep_previous(Changeset.t, module, Uploader.t, keyword) :: {:ok, any} | {:error, any()} def update_with_file_keep_previous(changeset, repo, uploader, opts \\ []) do Multi.new() |> Multi.run(:update, fn _repo, _ -> repo.update(changeset, opts) end) |> Multi.run(:store_files, fn _repo, %{update: record} -> uploader.store_files(record) end) |> repo.transaction() |> convert_result() end @doc """ Deletes the record and all of its files. This is not in a database transaction, since the delete operation never returns errors. """ @spec delete_with_file(any, module, Uploader.t, keyword) :: {:ok, any} | {:error, any()} def delete_with_file(record_or_changeset, repo, uploader, opts \\ []) do case repo.delete(record_or_changeset, opts) do {:ok, record} -> uploader.delete_files(record) {:error, error} -> {:error, error} end end defp convert_result({:error, _, msg, _}), do: {:error, msg} defp convert_result({:ok, %{insert: record}}), do: {:ok, record} defp convert_result({:ok, %{update: record}}), do: {:ok, record} end