Bow.Ecto behaviour (Bow v0.4.1) View Source
Integration of Bow.Uploader
with Ecto.Schema
Usage
# Add `use Bow.Ecto` to the uploader
defmodule MyApp.UserAvatarUploader do
use Bow.Uploader
use Bow.Ecto # <---- HERE
# file.scope will be the user struct
def store_dir(file) do
"users/#{file.scope.id}/avatar"
end
end
# add avatar field to users table
defmodule MyApp.Repo.Migrations.AddAvatarToUsers do
use Ecto.Migration
def change do
alter table(:users) do
add :avatar, :string
end
end
end
# use `MyApp.UserAvatarUploader.Type` as field type
defmodule MyApp.User do
schema "users" do
field :email, :string
field :avatar, MyApp.UserAvatarUploader.Type <-- HERE
end
def changeset(model \ %__MODULE__{}, params) do
model
|> cast(params, [:email, :avatar])
|> Bow.Ecto.validate() # <---- HERE
end
end
# create user and save files
changeset = User.changeset(%User{}, params)
with {:ok, user} <- Repo.insert(changeset),
{:ok, user, _} <- Bow.Ecto.store(user) do
{:ok, user}
end
Link to this section Summary
Callbacks
Customize incoming file.
Functions
Download remote files for given fields, i.e.
params["remote_avatar_url"] = "http://example.com/some/file.png"
Copy file from one record to another
Delete record files from storage
Load file from storage
Store files assigned to uploaders in Ecto Schema or Changeset.
Same as store/1
but raises an exception in case of upload error
Generate URL for record & field
Validate changeset using uploader's validate/1
function
Link to this section Callbacks
Specs
Customize incoming file.
This is the place to do custom file name transformation, since filename/2
is used both when uploading AND generating urls
Example - change file name to include timestamp
defmodule MyAvatarUploader do
use Bow.Uploader
use Bow.Ecto
def cast(file) do
ts = DateTime.utc_now |> DateTime.to_unix
Bow.set(file, :rootname, "avatar_#{ts}")
end
end
Link to this section Functions
cast_uploads(changeset, params, fields, client \\ %Tesla.Client{})
View SourceSpecs
cast_uploads(any(), map(), list(), Tesla.Client.t()) :: Ecto.Changeset.t()
Download remote files for given fields, i.e.
params["remote_avatar_url"] = "http://example.com/some/file.png"
Example
changeset
|> cast(params, [:name, :avatar])
|> Bow.Ecto.cast_uploads(params, [:avatar])
Specs
copy(src :: Ecto.Schema.t(), field :: atom(), dst :: Ecto.Schema.t()) :: {:ok, map()} | {:error, any()}
Copy file from one record to another
Fields do not have be the same unless they use the same uploader
Example
user1 = Repo.get(1)
user2 = Repo.get(2)
Ecto.Bow.copy(user1, :avatar, user2)
Specs
delete(model()) :: {:ok, model()} | {:error, any()}
Delete record files from storage
Example
user = Repo.get(...)
user
|> Repo.delete!()
|> Bow.Ecto.delete()
Specs
download_params(map(), list(), Tesla.Client.t()) :: map()
Specs
load(Ecto.Schema.t(), field :: atom()) :: {:ok, Bow.t()} | {:error, any()}
Load file from storage
Example
user = Repo.get(...)
case Bow.Ecto.load(user, :avatar) do
{:ok, file} -> # file.path is populated with tmp path
{:error, reason} -> # handle load error
end
Specs
Store files assigned to uploaders in Ecto Schema or Changeset.
In order to understand how to properly use store/1
function you need to read these few points:
- Ecto does not have callbacks (like
after_save
etc) Ecto.Changeset.prepare_changes
is run before data is saved into database, so when inserting a new record it will not have a primary key (id)- Uploading during type casting is a bad idea
- You do want to use record primary key in storage directory definition
- You don't want to upload the same file multiple time, even if it hasn't changed
You need to pass inserted/updated record since the changeset lacks primary key. When updating Bow.Ecto will upload only these files that were changed.
changeset = User.create_changeset(%User{}, params)
with {:ok, user} <- Repo.insert(changeset)
{:ok, _} <- Bow.Ecto.store(user) do # pass record here, not changeset
{:ok, user}
end
Creating
user = User.changeset(params)
with {:ok, user} <- Repo.insert(user),
{:ok, user, results} <- Bow.Ecto.store(user) do
# ...
else
{:error, reason} -> # handle error
end
Updating
user = User.changeset(user, params)
with {:ok, user} <- Repo.update(user),
{:ok, user, results} <- Bow.Ecto.store(user) do
# ...
else
{:error, reason} -> # handle error
end
There is also store!/1
function that will raise error instead of returning :ok/:error
tuple.
Specs
Same as store/1
but raises an exception in case of upload error
Creating
%User{}
|> User.changeset(params)
|> Repo.insert!()
|> Bow.Ecto.store!()
Updating
user
|> User.changeset(params)
|> Repo.update!()
|> Bow.Ecto.store!()
Specs
url(Ecto.Schema.t(), atom()) :: String.t() | nil
Generate URL for record & field
Specs
url(Ecto.Schema.t(), atom(), atom() | list()) :: String.t() | nil
Specs
url(Ecto.Schema.t(), atom(), atom(), list()) :: String.t() | nil
Specs
validate(Ecto.Changeset.t()) :: Ecto.Changeset.t()
Validate changeset using uploader's validate/1
function
Example
def changeset(model, params) do
model
|> cast(params, [:name, :avatar])
|> Bow.Ecto.validate()
end