Elixir phoenix file upload
This post hasn't been updated for 7 years
Đối với việc làm server thì việc upload các file dữ liệu hẳn là một việc không xa lạ gì với chúng ta. Hôm nay, mình sẽ cùng các bạn tìm hiểu về cách để upload một file lên server và để làm quen với nó mình sẽ làm ví dụ về việc upload avatar cho user. Việc này mình sẽ tiếp tục với project từ trước đó mà mình với các bạn đã làm (tại đây).
Tạo model
Mình sẽ làm ví dụ với trường avatar
của bảng users
. Do lần trước chúng ta đã tạo bảng user rồi nên bây giờ chỉ cần thêm trường avatar vào bảng user là xong. Với việc sử dụng để lưu file thì trường đó trong db (mysql) sẽ là string.
Để thêm một trường vào database ta sử dụng lệnh sau để tạo file migrate:
mix ecto.gen.migration add_avatar_to_users
=> Ta sẽ có được một file migrate như sau:
# priv/repo/migrations/20170202064713_add_avatar_to_users.exs
defmodule HelloPhoenix.Repo.Migrations.AddAvatarToUsers do
use Ecto.Migration
def change do
end
end
Khi đó ta muốn thêm hay xóa dữ liệu thì sẽ thêm các câu lệnh vào hàm change. Ví dụ ta thêm cột avatar kiểu string vào bang users thì sẽ viết như sau:
def change do
alter table(:users) do
add :avatar, :string
end
end
Uploader với lib arc và arc_ecto
Cũng giống như ruby on rails. Ta cũng tạo một uploader chung để các trường có kiểu file update sẽ sử dụng chung config khi cần để ta sử dụng.
Ở đây, ta sẽ tạo một uploader là avatar và ta cũng sử dụng thư viện arc
và arc_ecto
tương tự như là carrierwave của rails.
Thêm lib arc
và arc_ecto
vào project:
# mix.exs
def application do
...
applications: [..., :arc_ecto]
...
end
defp deps do
[ ...
{:arc_ecto, "0.3.2"},
{:arc, "0.2.0"}
]
end
Đừng quên chạy mix deps.get
để update 2 lib đó.
Nếu bạn sử dụng S3 để lưu file thì sử dụng thêm các lib sau:
defp deps do
[
...
ex_aws: "~> 1.0.0",
hackney: "1.6.1",
poison: "~> 2.0",
sweet_xml: "~> 0.5"
]
end
def application do
[
...
applications: [
...,
:ex_aws,
:hackney,
:poison
]
]
end
Bây giờ ta tạo uploader avatar bằng lệnh:
mix arc.g avatar
Khi đó hệ thống sẽ tự tạo cho ta 1 file avatar default và ta phải thêm bớt nó theo ý của mình:
# web/uploaders/avatar.ex
defmodule HelloPhoenix.Avatar do
use Arc.Definition
use Arc.Ecto.Definition
def __storage, do: Arc.Storage.Local
# Include ecto support (requires package arc_ecto installed):
@versions [:original]
# To add a thumbnail version:
# @versions [:original, :thumb]
# Whitelist file extensions:
# def validate({file, _}) do
# ~w(.jpg .jpeg .gif .png) |> Enum.member?(Path.extname(file.file_name))
# end
# Define a thumbnail transformation:
# def transform(:thumb, _) do
# {:convert, "-strip -thumbnail 250x250^ -gravity center -extent 250x250 -format png"}
# end
# Override the persisted filenames:
# def filename(version, _) do
# version
# end
# Override the storage directory:
def storage_dir(version, {file, scope}) do
"uploads/user/avatars"
end
# Provide a default URL if there hasn't been a file uploaded
# def default_url(version, scope) do
# "/images/avatars/default_#{version}.png"
# end
end
Với ví dụ trên thì mình thêm 2 dòng
use Arc.Ecto.Definition
def __storage, do: Arc.Storage.Local
mục đích là để lưu lại file dưới local. Nếu không các bạn có thể config với S3
config :arc,
bucket: {:system, "S3_BUCKET"}
config :ex_aws,
access_key_id: [{:system, "AWS_ACCESS_KEY_ID"}, :instance_role],
secret_access_key: [{:system, "AWS_SECRET_ACCESS_KEY"}, :instance_role]
và sửa lại hàm
def storage_dir(version, {file, scope}) do
"uploads/users/avatars/#{scope.id}"
end
=>
def storage_dir(version, {file, scope}) do
"uploads/user/avatars"
end
Do mình test nên để vậy cho dẽ theo dõi. Như vậy là đã tạo xong uploader và việc còn lại là mount trường dữ liệu tương ứng của model với uploader đó là xong.
#web/models/user.ex
...
use Arc.Ecto.Model
schema "users" do
...
field :avatar, HelloPhoenix.Avatar.Type
...
end
Vậy là xong với việc tạo uploader và mount chúng với dữ liệu tương ứng. Việc còn lại của ta chính là tạo view, controller để upload file lên và hiển thị chúng ra là xong.
View and controller
Việc này thì giống như việc tạo các controller và view thông thường khác. Chỉ khác biệt ở việc lấy đường dẫn của avatar. Không giống như config trên S3 ta định nghĩa host của url. Việc lưu file ở locale thì ta chỉ có thể lấy được path của url đó. Nên khi ta sử dụng thì cần chú ý đường dẫn. VD:
<img src="/<%= HelloPhoenix.Avatar.url({@user.avatar, @user}) %>", height="40" width="40">
Ta luôn phải thêm /
vào đằng trước để link đúng là http://localhost:3000/uploads/xxx
Khi không thêm thì nó sẽ là đường dẫn hiện tại + '/uploads/...' Như vậy sẽ không có được link đúng.
Khi upload file lên thì ta phải config thêm path của thư mục chứa file đó để có thể truy nhập đến. Chú ý là ta thêm dòng config sau chứ ko phải là sửa lại dòng cũ nhé:
# lib/hello_phoenix/endpoint.ex
...
plug Plug.Static, at: "/uploads", from: Path.expand('./uploads'), gzip: false
Ta dùng avatar như file field bình thường
<div class="form-group">
<label>Photo</label>
<%= file_input f, :avatar, class: "form-control" %>
</div>
Như vậy là ta đã xong việc upload và hiển thị file (ảnh) ra khi sử dụng lib arc
với arc_ecto
.
Ngoài ra các bạn có thể convert file, validate file, có các version :original, :thumb của 1 file gửi lên ...
Việc đi sâu vào chi tiết các bạn có thể tham khảo tại https://github.com/stavro/arc
Source đầy đủ tại đây Demo
Tài liệu tham khảo
http://www.phoenixframework.org/docs/file-uploads https://github.com/stavro/arc https://smashingboxes.com/blog/image-upload-in-phoenix/
All Rights Reserved