この記事は、Elixir その2 Advent Calendar 2020 2日目です。
前日は[87, 101, 32, 97, 114, 101, 32, 116, 104, 101, 32, 65, 108, 99, 104, 101, 109, 105, 115, 116, 115, 44, 32, 109, 121, 32, 102, 114, 105, 101, 110, 100, 115, 33]でした。
2020/11/22 追記
- Phoenix LiveView uploadsと題して、 https://dev.to/ にも英語で書いてみました
はじめに
- Here's a little sneak peek of Phoenix LiveView uploads を動かしたいです
- 2020/11/21現在、動作することを確認しました !!!
-
2020/10/24現在、絶賛レビュー中です- Add live uploads #1184
- 上記のプルリクは、2020/11/6にマージされています
- が、2020/11/7現在、私はまだアップロード動作を確認できていません
- といいますのもまだまだ更新は続いているようで、たとえばDo not recover forms on first join #1211というプルリクが続いたりしています
- 試してみたことの記録を残しておきます
- 先のプルリクエストは絶賛レビュー中で日々コミットが増えている状況ですので、また明日試すと結果は変わっているかもしれません
-
mix deps.update --all
で最新の更新内容が取り込めます
履歴
-
2020/11/21
- Phoenix LiveView 0.15.0でuploadsは対応されました
-
2020/11/7
- 486c4accf6f792d777fe61427e86d69a4025f369まで試しています
- 結果は
Phoenix.LiveView.UploadChannel.handle_in/3
の同じところでエラーがでています -
cd deps/phoenix_live_view/assets/ && npm install && node node_modules/webpack/bin/webpack.js --mode development
しています - いまはこうしておかないと、
deps/phoenix_live_view/assets/js/phoenix_live_view.js
の変更が自身のアプリにとりこまれないからです - 正式リリース時には不要な手順になるとおもいます
-
2020/10/24
- f2aa53eb21cbe47042f764f6c7e0d26a01d9ee66まで試しています
(FunctionClauseError) no function clause matching in Phoenix.LiveView.UploadChannel.handle_in/3
Add live uploads #1184にコメントして取り込んでいただいたもの
- GitHubのsuggestionで修正案を書いてみました(勝手にレビューに参加しています)
- なんとか動かそうとドキュメントを読んでいて気づいた誤りにsuggestionをしてみましたというところです
- Update lib/phoenix_live_view/helpers.ex
- Update lib/phoenix_live_view.ex
- Update guides/server/uploads.md
0. インストール
- Installationに従って進めてください
1. プロジェクト作成
$ mix phx.new demo
$ cd demo
$ mix setup
2. Phoenix LiveViewは0.15.0
mix.exs
--- a/mix.exs
+++ b/mix.exs
@@ -37,7 +37,7 @@ defmodule Demo.MixProject do
{:phoenix_ecto, "~> 4.1"},
{:ecto_sql, "~> 3.4"},
{:postgrex, ">= 0.0.0"},
- {:phoenix_live_view, "~> 0.14.6"},
+ {:phoenix_live_view, "~> 0.15.0"},
{:floki, ">= 0.27.0", only: :test},
{:phoenix_html, "~> 2.11"},
{:phoenix_live_reload, "~> 1.2", only: :dev},
- オプションの詳しいところが気になる方は、
mix help deps
でご確認ください
$ mix deps.get
3. Uploadsを利用する
- guides/server/uploads.mdを参考にしています
lib/demo_web/router.ex
@@ -18,6 +18,7 @@ defmodule DemoWeb.Router do
pipe_through :browser
live "/", PageLive, :index
+ live "/upload", UploadLive, :index
end
lib/demo_web/live/upload_live.ex
defmodule DemoWeb.UploadLive do
use DemoWeb, :live_view
@impl true
def mount(_params, _session, socket) do
{:ok,
socket
|> assign(:uploaded_files, [])
|> allow_upload(:avatar, accept: :any, max_entries: 3)}
end
def handle_event("validate", params, socket) do
{:noreply, socket}
end
@impl true
def handle_event("save", params, socket) do
uploaded_files =
consume_uploaded_entries(socket, :avatar, fn %{path: path}, _entry ->
dest = Path.join("priv/static/uploads", Path.basename(path))
File.cp!(path, dest)
Routes.static_path(socket, "/uploads/#{Path.basename(dest)}")
end)
{:noreply, update(socket, :uploaded_files, &(&1 ++ uploaded_files))}
end
end
lib/demo_web/live/upload_live.html.leex
<h1>Uploads</h1>
<%= for {_ref, err} <- @uploads.avatar.errors do %>
<p class="alert"><%= Phoenix.Naming.humanize(err) %></p>
<% end %>
<%= for entry <- @uploads.avatar.entries do %>
<%= entry.client_name %> - <%= entry.progress %>%
<%= live_img_preview entry, width: 75 %>
<% end %>
<form phx-submit="save" phx-change="validate">
<%= live_file_input @uploads.avatar %>
<button type="submit">Upload</button>
</form>
-
/upload
にアクセスすると以下のような感じで表示されます
- 試しにファイルを一つ選択してみます
- UPLOADボタンを押す!
- こんなエラーがでました
[error] GenServer #PID<0.629.0> terminating
** (File.CopyError) could not copy from "/var/folders/q6/5rpg5x9n6rx6357m9vztp_w00000gp/T//plug-1605/live_view_upload-1605895070-182994678098385-1" to "priv/static/uploads/live_view_upload-1605895070-182994678098385-1": no such file or directory
(elixir 1.10.4) lib/file.ex:814: File.cp!/3
(demo 0.1.0) lib/demo_web/live/upload_live.ex:21: anonymous fn/3 in DemoWeb.UploadLive.handle_event/3
(phoenix_live_view 0.15.0) lib/phoenix_live_view/upload_channel.ex:85: Phoenix.LiveView.UploadChannel.handle_call/3
(phoenix 1.5.7) lib/phoenix/channel/server.ex:269: Phoenix.Channel.Server.handle_call/3
(stdlib 3.13) gen_server.erl:706: :gen_server.try_handle_call/4
(stdlib 3.13) gen_server.erl:735: :gen_server.handle_msg/6
(stdlib 3.13) proc_lib.erl:226: :proc_lib.init_p_do_apply/3
Last message (from #PID<0.628.0>): {:consume, %Phoenix.LiveView.UploadEntry{cancelled?: false, client_last_modified: nil, client_name: "photo.png", client_size: 14517, client_type: "image/png", done?: true, preflighted?: true, progress: 100, ref: "0", upload_config: :avatar, upload_ref: "phx-FklJDbRnhJBw6wNh", uuid: "1093de58-ca7a-4466-b20d-accf0f470745", valid?: true}, #Function<0.15802900/2 in DemoWeb.UploadLive.handle_event/3>}
State: %Phoenix.Socket{assigns: %{chunk_timeout: 10000, chunk_timer: nil, done?: true, handle: #PID<0.630.0>, live_view_pid: #PID<0.628.0>, max_file_size: 14517, path: "/var/folders/q6/5rpg5x9n6rx6357m9vztp_w00000gp/T//plug-1605/live_view_upload-1605895070-182994678098385-1", uploaded_size: 14517}, channel: Phoenix.LiveView.UploadChannel, channel_pid: #PID<0.629.0>, endpoint: DemoWeb.Endpoint, handler: Phoenix.LiveView.Socket, id: nil, join_ref: "9", joined: true, private: %{connect_info: %{session: %{"_csrf_token" => "yw03HO6sYtcwV-OPt41fHesj"}}, log_handle_in: false, log_join: :info}, pubsub_server: Demo.PubSub, ref: nil, serializer: Phoenix.Socket.V2.JSONSerializer, topic: "lvu:0", transport: :websocket, transport_pid: #PID<0.623.0>}
Client #PID<0.628.0> is alive
(stdlib 3.13) gen.erl:208: :gen.do_call/4
(elixir 1.10.4) lib/gen_server.ex:1020: GenServer.call/3
(phoenix_live_view 0.15.0) lib/phoenix_live_view/upload_channel.ex:14: Phoenix.LiveView.UploadChannel.consume/3
(elixir 1.10.4) lib/enum.ex:1396: Enum."-map/2-lists^map/1-0-"/2
(demo 0.1.0) lib/demo_web/live/upload_live.ex:19: DemoWeb.UploadLive.handle_event/3
(phoenix_live_view 0.15.0) lib/phoenix_live_view/channel.ex:338: anonymous fn/3 in Phoenix.LiveView.Channel.view_handle_event/3
(telemetry 0.4.2) /Users/yamauchi/Documents/13_Elixir/phoenix_projects/demo/deps/telemetry/src/telemetry.erl:262: :telemetry.span/3
(phoenix_live_view 0.15.0) lib/phoenix_live_view/channel.ex:203: Phoenix.LiveView.Channel.handle_info/2
(stdlib 3.13) gen_server.erl:680: :gen_server.try_dispatch/4
(stdlib 3.13) gen_server.erl:756: :gen_server.handle_msg/6
(stdlib 3.13) proc_lib.erl:226: :proc_lib.init_p_do_apply/3
[error] GenServer #PID<0.628.0> terminating
** (stop) exited in: GenServer.call(#PID<0.629.0>, {:consume, %Phoenix.LiveView.UploadEntry{cancelled?: false, client_last_modified: nil, client_name: "photo.png", client_size: 14517, client_type: "image/png", done?: true, preflighted?: true, progress: 100, ref: "0", upload_config: :avatar, upload_ref: "phx-FklJDbRnhJBw6wNh", uuid: "1093de58-ca7a-4466-b20d-accf0f470745", valid?: true}, #Function<0.15802900/2 in DemoWeb.UploadLive.handle_event/3>}, 5000)
** (EXIT) an exception was raised:
** (File.CopyError) could not copy from "/var/folders/q6/5rpg5x9n6rx6357m9vztp_w00000gp/T//plug-1605/live_view_upload-1605895070-182994678098385-1" to "priv/static/uploads/live_view_upload-1605895070-182994678098385-1": no such file or directory
(elixir 1.10.4) lib/file.ex:814: File.cp!/3
(demo 0.1.0) lib/demo_web/live/upload_live.ex:21: anonymous fn/3 in DemoWeb.UploadLive.handle_event/3
(phoenix_live_view 0.15.0) lib/phoenix_live_view/upload_channel.ex:85: Phoenix.LiveView.UploadChannel.handle_call/3
(phoenix 1.5.7) lib/phoenix/channel/server.ex:269: Phoenix.Channel.Server.handle_call/3
(stdlib 3.13) gen_server.erl:706: :gen_server.try_handle_call/4
(stdlib 3.13) gen_server.erl:735: :gen_server.handle_msg/6
(stdlib 3.13) proc_lib.erl:226: :proc_lib.init_p_do_apply/3
(elixir 1.10.4) lib/gen_server.ex:1023: GenServer.call/3
(phoenix_live_view 0.15.0) lib/phoenix_live_view/upload_channel.ex:14: Phoenix.LiveView.UploadChannel.consume/3
(elixir 1.10.4) lib/enum.ex:1396: Enum."-map/2-lists^map/1-0-"/2
(demo 0.1.0) lib/demo_web/live/upload_live.ex:19: DemoWeb.UploadLive.handle_event/3
(phoenix_live_view 0.15.0) lib/phoenix_live_view/channel.ex:338: anonymous fn/3 in Phoenix.LiveView.Channel.view_handle_event/3
(telemetry 0.4.2) /Users/yamauchi/Documents/13_Elixir/phoenix_projects/demo/deps/telemetry/src/telemetry.erl:262: :telemetry.span/3
(phoenix_live_view 0.15.0) lib/phoenix_live_view/channel.ex:203: Phoenix.LiveView.Channel.handle_info/2
(stdlib 3.13) gen_server.erl:680: :gen_server.try_dispatch/4
(stdlib 3.13) gen_server.erl:756: :gen_server.handle_msg/6
(stdlib 3.13) proc_lib.erl:226: :proc_lib.init_p_do_apply/3
Last message: %Phoenix.Socket.Message{event: "event", join_ref: "4", payload: %{"event" => "save", "type" => "form", "value" => ""}, ref: "12", topic: "lv:phx-FklJDajpq0ivUgrD"}
State: %{components: {%{}, %{}, 1}, join_ref: "4", serializer: Phoenix.Socket.V2.JSONSerializer, socket: #Phoenix.LiveView.Socket<assigns: %{flash: %{}, live_action: :index, uploaded_files: [], uploads: %{__phoenix_refs_to_names__: %{"phx-FklJDbRnhJBw6wNh" => :avatar}, avatar: #Phoenix.LiveView.UploadConfig<accept: :any, ...>}}, changed: %{}, endpoint: DemoWeb.Endpoint, id: "phx-FklJDajpq0ivUgrD", parent_pid: nil, root_pid: #PID<0.628.0>, router: DemoWeb.Router, view: DemoWeb.UploadLive, ...>, topic: "lv:phx-FklJDajpq0ivUgrD", transport_pid: #PID<0.623.0>, upload_names: %{avatar: {"phx-FklJDbRnhJBw6wNh", nil}}, upload_pids: %{#PID<0.629.0> => {"phx-FklJDbRnhJBw6wNh", "0", nil}}}
[error] GenServer #PID<0.631.0> terminating
** (stop) exited in: GenServer.call(#PID<0.628.0>, {:phoenix, :register_entry_upload, %{channel_pid: #PID<0.631.0>, cid: nil, entry_ref: "0", ref: "phx-FklJDbRnhJBw6wNh"}}, 5000)
** (EXIT) no process: the process is not alive or there's no process currently associated with the given name, possibly because its application isn't started
(elixir 1.10.4) lib/gen_server.ex:1023: GenServer.call/3
(phoenix_live_view 0.15.0) lib/phoenix_live_view/upload_channel.ex:28: Phoenix.LiveView.UploadChannel.join/3
(phoenix 1.5.7) lib/phoenix/channel/server.ex:376: Phoenix.Channel.Server.channel_join/4
(phoenix 1.5.7) lib/phoenix/channel/server.ex:298: Phoenix.Channel.Server.handle_info/2
(stdlib 3.13) gen_server.erl:680: :gen_server.try_dispatch/4
(stdlib 3.13) gen_server.erl:756: :gen_server.handle_msg/6
(stdlib 3.13) proc_lib.erl:226: :proc_lib.init_p_do_apply/3
Last message: {Phoenix.Channel, %{"token" => "SFMyNTY.g2gDaAJhBHQAAAADZAADY2lkZAADbmlsZAADcGlkWGQADW5vbm9kZUBub2hvc3QAAAJ0AAAAAAAAAABkAANyZWZoAm0AAAAUcGh4LUZrbEpEYlJuaEpCdzZ3TmhtAAAAATBuBgAyI87mdQFiAAFRgA.BLOubQZKWUBTDL0TroHDprRup-O21gKvFnUzrDj9AEo"}, {#PID<0.623.0>, #Reference<0.2303790502.3383492609.21550>}, %Phoenix.Socket{assigns: %{}, channel: Phoenix.LiveView.UploadChannel, channel_pid: nil, endpoint: DemoWeb.Endpoint, handler: Phoenix.LiveView.Socket, id: nil, join_ref: "13", joined: false, private: %{connect_info: %{session: %{"_csrf_token" => "yw03HO6sYtcwV-OPt41fHesj"}}}, pubsub_server: Demo.PubSub, ref: nil, serializer: Phoenix.Socket.V2.JSONSerializer, topic: "lvu:0", transport: :websocket, transport_pid: #PID<0.623.0>}}
State: #Reference<0.2303790502.3383492609.21552>
[error] exited in: GenServer.call(#PID<0.628.0>, {:phoenix, :register_entry_upload, %{channel_pid: #PID<0.631.0>, cid: nil, entry_ref: "0", ref: "phx-FklJDbRnhJBw6wNh"}}, 5000)
** (EXIT) no process: the process is not alive or there's no process currently associated with the given name, possibly because its application isn't started
[error] GenServer #PID<0.632.0> terminating
** (KeyError) key "phx-FklJDbRnhJBw6wNh" not found in: %{"phx-FklJDyZBcshtwQPB" => :avatar}
:erlang.map_get("phx-FklJDbRnhJBw6wNh", %{"phx-FklJDyZBcshtwQPB" => :avatar})
(phoenix_live_view 0.15.0) lib/phoenix_live_view/upload.ex:166: Phoenix.LiveView.Upload.get_upload_by_ref!/2
(phoenix_live_view 0.15.0) lib/phoenix_live_view/upload.ex:107: Phoenix.LiveView.Upload.update_progress/4
(phoenix_live_view 0.15.0) lib/phoenix_live_view/channel.ex:139: anonymous fn/4 in Phoenix.LiveView.Channel.handle_info/2
(phoenix_live_view 0.15.0) lib/phoenix_live_view/channel.ex:992: Phoenix.LiveView.Channel.write_socket/4
(phoenix_live_view 0.15.0) lib/phoenix_live_view/channel.ex:137: Phoenix.LiveView.Channel.handle_info/2
(stdlib 3.13) gen_server.erl:680: :gen_server.try_dispatch/4
(stdlib 3.13) gen_server.erl:756: :gen_server.handle_msg/6
(stdlib 3.13) proc_lib.erl:226: :proc_lib.init_p_do_apply/3
Last message: %Phoenix.Socket.Message{event: "progress", join_ref: "16", payload: %{"entry_ref" => "0", "event" => nil, "progress" => %{"error" => "failed"}, "ref" => "phx-FklJDbRnhJBw6wNh"}, ref: "17", topic: "lv:phx-FklJDajpq0ivUgrD"}
State: %{components: {%{}, %{}, 1}, join_ref: "16", serializer: Phoenix.Socket.V2.JSONSerializer, socket: #Phoenix.LiveView.Socket<assigns: %{flash: %{}, live_action: :index, uploaded_files: [], uploads: %{__phoenix_refs_to_names__: %{"phx-FklJDyZBcshtwQPB" => :avatar}, avatar: #Phoenix.LiveView.UploadConfig<accept: :any, ...>}}, changed: %{}, endpoint: DemoWeb.Endpoint, id: "phx-FklJDajpq0ivUgrD", parent_pid: nil, root_pid: #PID<0.632.0>, router: DemoWeb.Router, view: DemoWeb.UploadLive, ...>, topic: "lv:phx-FklJDajpq0ivUgrD", transport_pid: #PID<0.623.0>, upload_names: %{}, upload_pids: %{}}
- フォルダがないと言っているようにみえたので、
priv/static/uploads
フォルダをこさえてみました - それから気を取り直してもう一度やってみました
- おっ! 今度はエラーがでなくなりました
-
priv/static/uploads
を覗くとファイルができています!!!-
Phoenix LiveView Uploads Deep Diveの動画でも
mkdir
しているのを確認しました
-
Phoenix LiveView Uploads Deep Diveの動画でも
-
open
コマンドで開いてみると画像が表示されることを確認しました
Wrapping Up
絶賛レビュー中なので待てということかもしれませんが、プルリクエストの対象ブランチが更新されたら取り込んでみて動作確認してみようとおもいますLiveView uploadsできたよ! という方はぜひコメントをください!-
Phoenix LiveView Uploads Deep Diveで動画で説明されているのでぜひ見てみてください(
私はまだみていない|> みた! ) - Enjoy Elixir !!!