基本的CRUD完成後,我們要試著挑戰稍微進階一點的功能,例如今天的上傳圖片。
首先我們在產品的model中新增圖片的欄位:
$ mix ecto.gen.migration add_photo_to_product
* creating priv/repo/migrations/20171228003556_add_photo_to_product.exs
編輯migration
def change do
alter table(:products) do
add :photo, :string, default: ""
end
end
model的schema要手動新增欄位
schema "products" do
field :description, :string
field :price, :integer
field :quantity, :integer
field :title, :string
field :photo, :string, default: ""
timestamps()
end
然後執行mix ecto.migrate
。
就像在ruby我們會使用像是carrierwave來處理圖片,在Elixir我們也會使用mix來處理圖片上傳,這邊我們安裝arc_ecto與arc,打開mix.exs
:
defp deps do
[
...
{:arc_ecto, "~> 0.4.4"},
{:arc, "~> 0.5.3"}
]
end
接著執行mix deps.get
來安裝套件。如果安裝時出現錯誤訊息:Hex dependency resolution failed
,可以試著更新所有相關的套件mix deps.update --all
有了arc以後,我們就可以建立upoader:
$ mix arc.g photo_uploader
...
* creating web/uploaders/photo_uploader.ex
因為這是Phoenix 2.1.5以前的結構,所以我們手動調整一下:
$ mkdir lib/shop_web/uploaders
$ mv web/uploaders/photo_uploader.ex lib/shop_web/uploaders/photo_uploader.ex
$ rm -r web
然後對檔案做下面的修正:
defmodule ShopWeb.PhotoUploader do
use Arc.Definition
use Arc.Ecto.Definition
@versions [:original, :thumb]
def transform(:thumb, _) do
{:convert, "-strip -thumbnail 100x100^ -gravity center -extent 100x100 -format png", :png}
end
def __storage, do: Arc.Storage.Local
def filename(version, {file, scope}), do: "#{version}-#{file.file_name}"
end
記得修改model的部分(貼上有加號的行數,加號記得刪除):
defmodule Shop.Goods.Product do
use Ecto.Schema
+use Arc.Ecto.Schema
import Ecto.Changeset
alias Shop.Goods.Product
schema "products" do
field :description, :string
field :price, :integer
field :quantity, :integer
field :title, :string
+field :photo, ShopWeb.PhotoUploader.Type
timestamps()
end
@doc false
def changeset(%Product{} = product, attrs) do
product
+|> cast_attachments(attrs, [:photo])
|> cast(attrs, [:title, :description, :quantity, :price])
|> validate_required([:title, :description, :quantity, :price])
end
end
完成後執行mix ecto.migrate
# lib/shop_web/templates/admin/product/form.html.eex
<%= form_for @changeset, @action, [multipart: true], fn f -> %>
...
<div class="form-group">
<%= label f, :photo, class: "control-label" %>
<%= file_input f, :photo, class: "form-control" %>
<%= error_tag f, :photo %>
</div>
...
打開畫面,就可以看到出現了photo檔案選擇的欄位
如果你試著上傳圖片,很可能會遇到跟我一樣的錯誤:找不到crypto.rand_bytes
方法,這時候請到deps/arc/lib/arc/transformations/convert.ex
,手動把他改為crypto.strong_rand_bytes
,然後編譯mix deps.compile
,這時候再重開伺服器,應該就可以解決了。
首先在新增下面這段,不要把原本的plug Plug.Static
覆蓋掉:
# lib/shop_web/endpoint.ex
plug Plug.Static,
at: "admin/uploads", from: Path.expand('./uploads'), gzip: false
接著在index.html.eex新增下面這段(或任何你希望出現圖片的地方)
...
<%= product.title %>
<%= if product.photo.file_name != "" do %>
<img class="thumbnail"
src="<%= Shop.PhotoUploader.url({product.photo, product}, :thumb) %>"/>
<% end %>
...
這樣圖片就可以顯示了
參考資料:上傳圖片