LoginSignup
17
1

More than 3 years have passed since last update.

LiveView uploadsを動かす 🎉🎉🎉(Elixir/Phoenix)

Last updated at Posted at 2020-10-24

この記事は、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 追記

はじめに

履歴

  • 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

Add live uploads #1184にコメントして取り込んでいただいたもの

0. インストール

1. プロジェクト作成

$ mix phx.new demo
$ cd demo
$ mix setup

2. Phoenix LiveView0.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を利用する

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にアクセスすると以下のような感じで表示されます

スクリーンショット 2020-10-24 23.54.33.png

  • 試しにファイルを一つ選択してみます
    • :lgtm::lgtm::lgtm:

スクリーンショット 2020-10-24 23.54.46.png

  • UPLOADボタンを押す!
  • こんなエラーがでました :sweat_smile:
[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を覗くとファイルができています!!!
  • openコマンドで開いてみると画像が表示されることを確認しました :tada::tada::tada:

Wrapping Up :qiitan:

  • 絶賛レビュー中なので待て:dog:ということかもしれませんが、プルリクエストの対象ブランチが更新されたら取り込んでみて動作確認してみようとおもいます
  • LiveView uploadsできたよ! という方はぜひコメントをください!
  • Phoenix LiveView Uploads Deep Diveで動画で説明されているのでぜひ見てみてください(私はまだみていない |> みた! :smile_cat: )
  • Enjoy Elixir !!! :fire::rocket::rocket::rocket:
17
1
0

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
17
1