# Uploadex

Uploadex is an Elixir library for handling uploads that integrates well with [Ecto](, [Phoenix]( and [Absinthe](

Documentation can be found at

## Installation

The package can be installed by adding `uploadex` to your list of dependencies in `mix.exs`:

def deps do
    {:uploadex, "~> 2.0.3"}

## Usage

Follow these steps to use Uploadex:

### 1: Uploader

This library relies heavily on pattern matching for configuration, so the first step is to define your Uploader configuration module:

defmodule MyApp.Uploader do
  @moduledoc false
  @behaviour Uploadex.Uploader

  alias MyAppWeb.Endpoint

  @impl true
  def get_fields(%User{}), do: :photo
  def get_fields(%Company{}), do: [:photo]

  @impl true
  def default_opts(Uploadex.FileStorage), do: [base_path: Path.join(:code.priv_dir(:my_app), "static/"), base_url: Endpoint.url()]
  def default_opts(Uploadex.S3Storage), do: [bucket: "my_bucket", region: "sa-east-1", upload_opts: [acl: :public_read]]

  @impl true
  def storage(%User{id: id}, :photo), do: {Uploadex.FileStorage, directory: "/uploads/users/#{id}"}
  def storage(%Company{}, _field), do: {Uploadex.S3Storage, directory: "/thumbnails"}

  # Optional:
  @impl true
  def accepted_extensions(%User{}, :photo), do: ~w(.jpg .png)
  def accepted_extensions(_any, _photo), do: :any

This example shows the configuration for the [Uploadex.FileStorage]( and [Uploadex.S3Storage]( implementations, but you are free to implement your own [Storage](

### 2: Config

Then, set your Upload module in your `config.exs`:

config :uploadex,
  uploader: MyApp.Uploader,
  repo: MyApp.Repo

Note that the `Repo` is only necessary if using [Uploadex]( context helper functions.

### 3: Schema

In your schema, use the Ecto Type [Uploadex.Upload](

schema "users" do
  field :name, :string
  field :photo, Uploadex.Upload

# No special cast is needed, and casting does not have any side effects.
def create_changeset(%User{} = user, attrs) do
  |> cast(attrs, [:name, :photo])

### 4: Enjoy!

Now, you can use the [Uploadex]( functions to handle your records with their files:

defmodule MyApp.Accounts do
  alias MyApp.Accounts.User
  alias MyApp.MyUploader

  def create_user(attrs) do
    |> User.create_changeset(attrs)
    |> Uploadex.create_with_file()

  def update_user(%User{} = user, attrs) do
    |> User.update_changeset(attrs)
    |> Uploadex.update_with_file(user)

  def delete_user(%User{} = user) do
    |> Ecto.Changeset.change()
    |> Uploadex.delete_with_file()

You can also use the [Resolver]( module to integrate with Absinthe when exposing the files through a GraphQL API.

For more flexibility, you can use the [Files]( module directly, which provides some extra functionalities, such as `get_temporary_file`, useful when the files are not publicly available.

## Motivation

Even though there already exists a library for uploading files that integrates with ecto (, this library was created because:

* arc_ecto does not support upload of binary files
* Uploadex makes it easier to deal with records that contain files without having to manage those files manually on every operation
* Using uploadex, the changeset operations have no side-effects and no special casting is needed
* Uploadex offers more flexibility by allowing to define different storage configurations for each struct in the application