Bow v0.3.2 Bow.Ecto behaviour 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
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
Callbacks
Customize incoming file.
Link to this section Functions
cast_uploads(changeset, params, fields, client \\ %Tesla.Client{})
View Sourcecast_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])
copy(src, src_field, dst)
View Sourcecopy(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)
delete(record)
View Sourcedelete(model()) :: {:ok, model()} | {:error, any()}
Delete record files from storage
Example
user = Repo.get(...)
user
|> Repo.delete!()
|> Bow.Ecto.delete()
download_params(params, fields, client \\ %Tesla.Client{})
View Sourcedownload_params(map(), list(), Tesla.Client.t()) :: map()
load(record, field)
View Sourceload(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
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.
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!()
url(record, field)
View Sourceurl(Ecto.Schema.t(), atom()) :: String.t() | nil
Generate URL for record & field
url(record, field, opts)
View Sourceurl(Ecto.Schema.t(), atom(), atom() | list()) :: String.t() | nil
url(record, field, version, opts)
View Sourceurl(Ecto.Schema.t(), atom(), atom(), list()) :: String.t() | nil
validate(changeset)
View Sourcevalidate(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
Link to this section Callbacks
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