概要
勉強のため、Phoenix1.6にライブラリを追加せずに、デフォルト設定のままファイルアップロードのコードを書いてみた。
昔のバージョン(1.3)の公式ドキュメントではチュートリアルにファイルアップロードの項目があったみたいなんだけど、
1.6.xには見当たらなかった?ので、せっかくなので記事にしてみた。
(どっか他の場所にあったらすみません。そっ閉じして下さい)
まずはPhoenixプロジェクトの作成
環境はこんな感じです。
$ elixir -v
Erlang/OTP 24 [erts-12.2] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [jit] [dtrace]
Elixir 1.13.1 (compiled with Erlang/OTP 24)
端末の任意の場所で以下のコマンドを実行
※ここで--database sqlite3
としているのは、SQLite3をDBとして使っているため。
後でアップロードしたファイルの格納先のパスを保存したりしようかなと思います。(予定は未定)
$ mix phx.new my_app --database sqlite3
<省略>
Fetch and install dependencies? [Yn] Y #Yを選択
<省略>
$
終了したら、案内に従って以下を実行
$ cd my_app
$ mix ecto.create
<省略>
$ iex -S mix phx.server
終了したらブラウザで http://127.0.0.1:4000/にアクセス。
画面が表示されたらOK
下準備 ファイルの新規作成と編集
とりあず、最低限3つのファイル(controller, view ,template)を作成し、
1つのファイル(router)を編集します。
1.templateの作成
アップロードフォーム画面を作成します。
my_app/lib/my_app_web/templates
ディレクトリの下に、upload
ディレクトリを作成し、
その中にform.html.heex
を作成します。
upload
ディレクトリを作るのを忘れずに
<%= form_for @conn, "/upload", [as: :upload, multipart: true], fn f -> %>
<div>
<%= file_input f, :input_file %>
</div>
<div>
<%= submit "送信" %>
</div>
<% end %>
2.viewの作成
my_app/lib/my_app_web/views
ディレクトリの下にupload_view.ex
を作成
記述は以下で完成です。
defmodule MyAppWeb.UploadView do
use MyAppWeb, :view
end
3.コントローラの作成
my_app/lib/my_app_web/controllers
ディレクトリの下にupload_controller.ex
を作成
この時点ではまだアップロードのロジックは書いてません。後で順を追って追記していきます。
まずファイルアップロードのフォームを表示させます。
defmodule MyAppWeb.UploadController do
use MyAppWeb, :controller
#フォームの表示
def form(conn, _params) do
render(conn, "form.html")
end
end
4.router.exにルーティング設定追加
my_app/lib/my_app_web/router.ex
を編集してパスとコントローラとその関数を紐づけます。
scope "/", MyAppWeb do
の中に一行追加します。
### 省略 ###
scope "/", MyAppWeb do
pipe_through :browser
get "/", PageController, :index
get "/upload-form", UploadController, :form #この行を追加
end
### 省略 ###
ここまで終わったらブラウザで http://127.0.0.1:4000/upload-formにアクセス。
シンプルすぎる画面が表示されました
ちょっとここでhtmlを開いてみてみます。
<form action="/upload" enctype="multipart/form-data" method="post">
<input name="_csrf_token" type="hidden" value="awJacXg0DmQiSXMsFhQ0XTkJJRwyFyku4xh82ezRFz5btux-XmSVKDGF">
<div>
<input id="upload_input_file" name="upload[input_file]" type="file">
</div>
<div>
<button type="submit">送信</button>
</div>
</form>
htmlを見るに、ファイルは/upload
にパラメータ名upload[input_file]
でPOSTで送信されることが
わかります。
それを受け取るロジックを書いていきます。
※ちなみに<input name="_csrf_token" ~
はCSRF(クロスサイト・リクエスト・フォージェリ)対策のために自動的に生成されるコードです。本題から外れていくので詳しくは触れません。
アップロードロジックの実装その1
1.コントローラーの編集
upload_controller.ex
に関数を追加します。
一旦これだけにしておきます。受け取った内容をIO.inspect
でコンソールに出してみます。
画面には"OK"だけ返します。
def upload_action(conn, %{"upload" => upload}) do
IO.inspect upload
json(conn, "OK")
end
ちなみに、ここで、actionという名前にしてますが、他のフレームワークのようにactionとつけなければ
ならない規約はありません。なんでもいいです。
2.routerの編集
router.ex
にルーティング設定を追加します。
### 省略 ###
scope "/", MyAppWeb do
pipe_through :browser
get "/", PageController, :index
get "/upload-form", UploadController, :form #追加した行
post "/upload", UploadController, :upload_action #この行をさらに追加
end
### 省略 ###
アップロードしてみる
ファイルはなんでもいいんですが、一応公開記事なので無難なものに汗
↓これをアップロードしてみます。
ここでコンソールをチェックしてみます。
%{
"input_file" => %Plug.Upload{
content_type: "image/png",
filename: "logo.png",
path: "/var/folders/cp/kv9jk2xx06gg24_0_38kbl900000gp/T//plug-1644/multipart-1644074153-276432714408854-4"
}
}
[info] Sent 200 in 8ms
変数upload
は、Mapであり、中身は"input_file"というキー名でPlug.Upload
構造体が格納されているようです。filename
はアップロードしたファイル名、path
は保存されたファイル名であることの察しがつきます。
( 思ったより情報少ないですね。ファイルサイズとかはpathに記載されてるファイルにアクセスして自分で取得せよってことなんでしょうかね)
では、ファイルを確認してみましょう。chromeでアクセス。しかし
アクセスできません。どうやら一時的に保存されるだけで処理が終了すると削除されてしまうようです。
なので、ファイルを他の場所に雑にコピーすることにします。
アップロードロジックの実装その2
コントローラを編集
upload_controller.ex
のdef upload_action
を編集します。
def upload_action(conn, %{"upload" => upload}) do
file = upload["input_file"]
cp_filename = "copy_" <> file.filename
File.copy(file.path, cp_filename)
map = Map.from_struct(file)
result = Map.put(map, :save_filename, cp_filename)
json(conn, result)
end
File.copy
だけいいんですが、せっかくなんで画面にファイル情報を返却させることにしました。
map = Map.from_struct(file)
で構造体をMapに変換し、(構造体からJsonに直接変換できない)
そのMap中に、保存したファイル名情報を追加したものをjson形式に変換して画面に返却します。
再びアップロード
フォームに戻り、画像を選択して送信
端末を確認すると。。
アップロードしたファイルが保存されています。
DBに保存したファイルのパスを格納したり、UUIDで保存するようにしたり色々出来そうですね。
でも、少し調べてみたらファイルアップロード関連のライブラリも結構あるようなんですよね。
多分、ライブラリ使った方がいいことがあるんでしょうね。