Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/recruitee/bow
File uploads for Elixir (with Ecto support)
https://github.com/recruitee/bow
attachment ecto elixir upload
Last synced: about 2 months ago
JSON representation
File uploads for Elixir (with Ecto support)
- Host: GitHub
- URL: https://github.com/recruitee/bow
- Owner: Recruitee
- License: mit
- Created: 2017-09-08T16:48:09.000Z (over 7 years ago)
- Default Branch: master
- Last Pushed: 2023-08-14T10:16:03.000Z (over 1 year ago)
- Last Synced: 2024-03-15T06:04:20.683Z (10 months ago)
- Topics: attachment, ecto, elixir, upload
- Language: Elixir
- Homepage:
- Size: 1.19 MB
- Stars: 26
- Watchers: 28
- Forks: 4
- Open Issues: 3
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# Bow
File uploads for Elixir
## Features
- Generate multiple dependent versions of a file
- Integration with Ecto
- Allow downloading remote files (`remote_avatar_url` params etc.)
- Multiple storage adapters (local disk, Amazon S3)## Installation
```elixir
def deps do
[
{:bow, "~> 0.4.3"},# for AWS S3 support
{:ex_aws, "~> 2.0"},
{:ex_aws_s3, "~> 2.0"},# for Bow.Exec
{:erlexec, "~> 1.7.0"}
]
end
```## Usage
### Minimal uploader definition
```elixir
defmodule MyUploader do
use Bow.Uploader# specify storage directory
def store_dir(_file) do
"uploads"
end
end
```### Full uploader example
```elixir
defmodule AttachmentUploader do
use Bow.Uploader# define what versions to generate for given file
def versions(_file) do
[:original, :thumb]
end# keep the origianal file name
def filename(file, :original), do: file.name# prepend "thumb_" for thumbnail
def filename(file, :thumb), do: "thumb_\#{file.name}"# do nothing with original file
def transform(file, :original), do: transform_original(file)# generate image thumbnail
def transform(source, target, :thumb) do
Bow.Exec.exec source, target,
"convert ${input} -strip -gravity Center -resize 250x175^ -extent 250x175 ${output}"
end# specify storage directory
def store_dir(file) do
"attachments/\#{file.scope.id}"
end# specify storage options
def store_options(_file) do
[acl: :public_read]
end
end
```### Usage with Ecto
```elixir
# 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.Migrationdef 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
enddef changeset(model \\ %__MODULE__{}, params) do
model
|> cast(params, [:email, :avatar])
# uncomment to add support for remote_avatar_url params
# |> Bow.Ecto.cast_uploads(params, [:avatar])
|> Bow.Ecto.validate() # optional validation using uploader rules
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
```### Getting file URL
With standalone uploaders:
```elixir
file = MyUploader.new("path/to/file.png")Bow.url(file) # => url of original file
Bow.url(file, :thumb) # => url of thumb version
```With Ecto integration:
```elixir
user = Repo.get(User, 1)Bow.Ecto.url(user, :avatar) # url of avatar original
Bow.Ecto.url(user, :avatar, :thumb) # url of avatar thumb
Bow.Ecto.url(user, :photo, :thumb, signed: true) # you can pass storage-specific options
```### Overwriting file name
You can change the file name using uploader's `cast/1` callback:
```elixir
defmodule TimestampUploader do
use Bow.Uploader
use Bow.Ectodef cast(file) do
# replace "myfile.png" with "avatar_12343456.png"
ts = DateTime.utc_now |> DateTime.to_unix
Bow.set(file, :rootname, "avatar_#{ts}")
enddef store_dir(_file), do: "timestamp"
end
```### Validation
You can overwrite `validate/1` to add validations for e.g. allowed extension.
```elixir
defmodule AvatarUploader do
# ...
def validate(%{ext: ext}) when ext in ~w(.jpg .png), do: :ok
def validate(_), do: {:error, :extension_not_allowed}
# ...
end
```### Using Bow in test environment
It is best to use local storage adapter when testing.
```elixir
# config/test.exs
config :bow,
storage: Bow.Storage.Local,
storage_prefix: "tmp/bow/"
```## Running Bow tests
```bash
mix test
```#### Testing ecto integration
```bash
# edit config/config.exs# create test database
MIX_ENV=test mix ecto.create# run tests
mix test --only ecto
```#### Testing S3 adapter
```bash
# install fake-s3 gem
gem install fakes3# start fake-s3 server
fakes3 -r tmp/s3 -p 4567# run tests
mix test --only s3
```