6
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Phoenix/Elixir シンプルなファイルアップロード

Posted at

概要

勉強のため、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

スクリーンショット 2022-01-30 11.17.29.png

下準備 ファイルの新規作成と編集

とりあず、最低限3つのファイル(controller, view ,template)を作成し、
1つのファイル(router)を編集します。

スクリーンショット 2022-02-05 22.17.24.png

1.templateの作成

アップロードフォーム画面を作成します。

my_app/lib/my_app_web/templatesディレクトリの下に、uploadディレクトリを作成し、
その中にform.html.heexを作成します。
uploadディレクトリを作るのを忘れずに
スクリーンショット 2022-02-05 22.38.41.png

<%= 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を編集してパスとコントローラとその関数を紐づけます。
スクリーンショット 2022-02-05 22.48.28.png

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にアクセス。
シンプルすぎる画面が表示されました
image.png

ちょっとここで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
### 省略 ###

アップロードしてみる

ファイルはなんでもいいんですが、一応公開記事なので無難なものに汗
↓これをアップロードしてみます。

logo.png

ファイルを選択し、送信。すると。。
スクリーンショット 2022-02-06 0.08.24.png

”OK”が返ってきました。
スクリーンショット 2022-02-06 0.18.14.png

ここでコンソールをチェックしてみます。

%{
  "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でアクセス。しかし
image.png

アクセスできません。どうやら一時的に保存されるだけで処理が終了すると削除されてしまうようです。
なので、ファイルを他の場所に雑にコピーすることにします。

アップロードロジックの実装その2

コントローラを編集

upload_controller.exdef 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形式に変換して画面に返却します。

再びアップロード

フォームに戻り、画像を選択して送信

スクリーンショット 2022-02-06 0.50.44.png

想定通り画面にファイル情報が返ってきました。
image.png

端末を確認すると。。

image.png

アップロードしたファイルが保存されています。

DBに保存したファイルのパスを格納したり、UUIDで保存するようにしたり色々出来そうですね。

でも、少し調べてみたらファイルアップロード関連のライブラリも結構あるようなんですよね。
多分、ライブラリ使った方がいいことがあるんでしょうね。

6
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?