Edited at

Phoenix LiveView によるカウンタの作り方【Elixir 入門者向け 翻訳】


訳者から読者への注

この記事は Dennis Beatty さんの書いた How to Create a Counter with Phoenix LiveView (Mar 19, 2019) を御本人の許可を得て日本語に翻訳した文書です。

大変簡潔なチュートリアルで、データベースも使ってませんので普段 RDB をお使いでない方でも簡単に LiveView を試せます。この文書だけでもお試しはできますが ご本人が実際にやって見せてる動画 もあるので、疑問に思ったらこれを観ながらやるのも良いかと思います。私は Elixir 1.8.1、Phoenix 1.4.2 の環境で、日本時間で 2019年3月20日に実行して滞りなくお試しできました。このチュートリアルは LiveView と同時に公開されたサンプルコードへの連続性もあるので、さらに多くのサンプルで学習することも可能です。

では以下から本文がはじまります。みなさん LiveView をお楽しみください!


編集履歴


  • 2019.03.22 公開

  • 2019.03.24 mix.exs に対して編集が必要な旨、npm の警告への対応、を追加

  • 2019.03.25 参考文献として @piacere_ex さんのチュートリアルの2番目を1番目とまとめて最後においた

  • 2019.03.26 @__hage さんの指摘で2箇所の間違いを修正

  • 2019.03.31 @piacere_ex さんのチュートリアルの3番目を追加

  • 2019.06.22 より検索で分かりやすいように、タイトルに「Elixir 入門者向け」を追加

  • 2019.08.04 @nunulk さんより mix.exs に手を加える必要がなくなったとの情報を頂いて該当部を修正

  • 2019.08.06 @nunulk さんより dev.exs と config.exs に対する編集が不要になったとの情報を頂いて該当部を修正



はじめに

Elixir 関連のツイートを追っかけてる方なら、おそらく Chris McCord さんがこの金曜日にリリースしたPhoenix LiveView のことはご存知でしょう1。Chris さん(とこれに貢献した方たち)は徹底したドキュメントに加えて、数々の使用例 〜これは特に私が感銘を受けたものですが〜 も同時に出すという素晴らしい仕事をしました。ここに出てくる使用例の一つによく似たカウンタの例を通して、みなさんに LiveView の御案内をしてみます。全体のそれぞれのステップを可能な限り単純にして説明してみます。何か質問があったら私に Twitter で聞いてみてください。


プロジェクトの生成

まず最初に live_view_counter という Phoenix を基にする Elixir プロジェクトを作りましょう。それには Phoenix generator を使います。今回はデータベースを使わないので Ecto なしで作りましょう。

mix phx.new live_view_counter --no-ecto

ここで [Y/n] で応答を求められます。依存関係を構築するのに Y を入力してください。そしてプロジェクトフォルダの live_view_counter に cd して mix.exs ファイルの deps 関数に以下の依存関係を追加してください。

{:phoenix_live_view, github: "phoenixframework/phoenix_live_view"}

その上で mix deps.get コマンドを実行して最新の依存関係にしてください。


訳注再追加 (2019.08.04)

以下の修正は不要になっていたとの情報を @nunulk さんよりいただきました。


訳注追加 (2019.03.24)

このままやると以下のエラーが出るようになりました。

Dependencies have diverged:

* phoenix (Hex package)
the dependency phoenix in mix.exs is overriding a child dependency:

> In mix.exs:
{:phoenix, "~> 1.4.0", [env: :prod, repo: "hexpm", hex: "phoenix"]}

> In deps/phoenix_live_view/mix.exs:
{:phoenix, [env: :prod, git: "https://github.com/phoenixframework/phoenix.git", branch: "v1.4"]}

Ensure they match or specify one of the above in your deps and set "override: true"
** (Mix) Can't continue due to errors on dependencies

この場合 mix.exs がこのようになってたら…


mix.exs

  defp deps do

[
{:phoenix, "~> 1.4.0"},
...
]

以下のように override: true を追加してください。


mix.exs

  defp deps do

[
{:phoenix, "~> 1.4.0", override: true},
...
]


基本設定

ここで認証に使うソルトを入れたくなります。これは中間者攻撃 (man-in-the-middle attack) を防ぐためのセキュリティ手法です。あなた用のソルトを作るのに mix phx.gen.secret 32 が使えます。それを config に入れてください。なおこれはお試し用なんで config/config.exs ファイルに直に書いたのを見せてますが、本番だったら見えるような場所に置いたりしません。2

config :live_view_counter, LiveViewCounterWeb.Endpoint,

...
live_view: [
signing_salt: "7HekGYwxATz33gM/rH9q2mV+uKJq5/Hu"
]

翻訳者注:以下の修正は不要になったとの指摘を @nunulk さんよりもらいました。2019.08.06

次の設定では、拡張子が .leex であるようなレンダリングファイル用の LiveView テンプレートエンジンを使うことを Phoenix に伝えます。以下の2行を追加してください。

config :phoenix,

template_engines: [leex: Phoenix.LiveView.Engine]

では lib/live_view_counter_web/router.ex ファイルに browser パイプライン (pipeline) への LiveView フラッシュプラグ (flash plug) を追加してください。さらに LiveViewCounterWeb.LayoutViewput_layout として追加することで、デフォルトのレイアウトに新たに作るライブビュー (live view) をラップします。3

pipeline :browser do

...
plug :fetch_flash
plug Phoenix.LiveView.Flash
...
plug :put_layout, {LiveViewCounterWeb.LayoutView, :app}
end

さて lib/live_view_counter_web.ex ファイルにいくつかのインポート (import) の記述をします。このファイルは Phoenix に隠されてる 魔術 の大きな部分です。このファイルはコントローラ (controller)、ビュー (view)、ルータ (router)、チャンネル (channel) を組み上げるためのエイリアス (alias) や関数をインポートします。役に立つ LiveView の関数をインポートするように書き換えてみます。

def view do

quote do
...
import Phoenix.LiveView, only: [live_render: 2, live_render: 3]
end
end

def router do
quote do
...
import Phoenix.LiveView.Router
end
end

次に lib/live_view_counter_web/endpoint.ex ファイルに、LiveView の更新をクライアントに送るためのウェブソケット (websocket) の記述を入れましょう。

defmodule LiveViewCounterWeb.Endpoint do

use Phoenix.Endpoint, otp_app: :live_view_counter

socket "/live", Phoenix.LiveView.Socket
...
end

そして LiveView に関する Javascript の依存性を assets/package.json に追加します。これでクライアントがバックエンドの LiveView と話すようになります。

{

...
"dependencies": {
"phoenix": "file:../deps/phoenix",
"phoenix_html": "file:../deps/phoenix_html",
"phoenix_live_view": "file:../deps/phoenix_live_view"
}
...
}

さあ cd assets && npm install コマンドを実行して依存してる Javascript のパッケージをインストールしましょう4。これで設定のほとんどは完了です。assets/js/app.js ファイルの一番最後に LiveView ソケットの接続を有効にする記述をしてください。

import LiveSocket from "phoenix_live_view"

let liveSocket = new LiveSocket("/live")
liveSocket.connect()

翻訳者注:以下の config/dev.exs ファイルに対する編集は必要なくなったとの指摘を @nunulk さんよりいただきました。(2019.08.06)

そして config/dev.exs ファイルに逐次リロード (live reload) 用の変更を施してください。これで LiveView テンプレートのリロードを連続的にできるようになります。

config :live_view_counter, LiveViewCounterWeb.Endpoint,

live_reload: [
patterns: [
...,
~r{lib/live_view_counter_web/live/.*(ex)$}
]
]

最後に(これは必須ではないですが)assets/css/app.css ファイルに以下を追加してデフォルトの CSS スタイルを取り込んでください。

@import "../../deps/phoenix_live_view/assets/css/live_view.css";

さあ、セットアップが全部完了です!

全部の設定を確認したら mix phx.server コマンドを実行してください。必要なコンパイルが実行されるので、ブラウザで http://localhost:4000 にアクセスしてみてください。Phoenix の画面が見えるはずです。コマンドを入力した端末画面には Replied phoenix:live_reload :ok と出てくるでしょう。5

...

[info] Replied Phoenix.LiveView.Socket :ok
[info] Replied phoenix:live_reload :ok

Phoenix LiveView がもうちょっとばかり安定したら Phoenix generator に取り込まれるだろうと想像してます。そうなったらこういった決まりきった一連の設定を毎回やる必要はなくなるでしょう。さて、ここからが本当に楽しいところです。次へ行きましょう。

Just after the making of the boilerplate


訳注追加 (2019.03.24)

asset フォルダ下で npm install コマンドを実行すると、こんなエラーが出てました。

$ npm install

npm WARN assets No description

added 1 package from 1 contributor and audited 14925 packages in 6.902s
found 4 vulnerabilities (2 low, 2 moderate)
run `npm audit fix` to fix them, or `npm audit` for details

これ無視して進めてもこのチュートリアルはできるようです。しかしながら気持ち悪いですね。

もう少し掘ると npm audit コマンドでどんな脆弱性があるのかわかります。指示では npm audit fix コマンドを実施するように言ってきますが、これでは治らず npm audit fix --force を実施する必要があります。

npm WARN using --force I sure hope you know what you are doing.

なんて言われますけど、脆弱性の表示はなくなります。

$ npm audit               

=== npm audit security report ===
found 0 vulnerabilities
in 7695 scanned packages



カウンタの作成

カウンタを作るには lib/live_view_counter_web/router.ex にあるデフォルトのルートを新しい live ルートに置き換えることからはじめます。

...

scope "/", LiveViewCounterWeb do
pipe_through :browser

live("/", CounterLive)
end
...

ルートの設定にあわせて新しいフォルダ livelib/live_view_counter_web の下に作成して、さらにその下に counter_live.ex ファイルを作ります。ファイル名の後ろに _live をつけるのは LiveView の慣習で、同様にコントローラ (controller) にはファイル名の後ろに _controller をつけます。LiveView のビュー (view) の生成を可能にするのに use Phoenix.LiveView を入れることで必要な関数を持って来ます。

defmodule LiveViewCounterWeb.CounterLive do

use Phoenix.LiveView
end

このモジュールに render 関数を加えます。この関数は実際の DOM の要素を描画する live モジュール上の重要なポイントです。これで、現在の値を表示するカウンタの描画と、カウンタ値を増減させるためのボタンの描画をします。

def render(assigns) do

~L"""
<div>
<h1>The count is: <%= @val %></h1>
<button phx-click="
dec">-</button>
<button phx-click="
inc">+</button>
</div>
"
""
end

複数行を挿入する先頭に ~L という見慣れないシジルがあるでしょう。これは LiveView テンプレートを定義する新しい文法です。ここまでやると端末画面に例外が発生してるのが見られます。

** (ArgumentError) assign @val not available in eex template.

これは render 関数の中で @val を使っているのに、値の設定をしていないからです。これを解決するのに LiveView の mount/2 関数を用います。この関数を counter_live.ex ファイル内の render 関数記述の後ろに足してください。

def mount(_session, socket) do

{:ok, assign(socket, :val, 0)}
end

Phoenix のチャンネルに値を設定するのにも同じ文法を使います。mount/2 関数はビューの描画の際にコントローラから起動され、セッションの値と LiveView のソケットを受け取り、ビューの初期画面の描画に必要なデータを生成します。mount/2 関数が動作を完了したのちに render/1 関数が起動されます。ということで、基本的にはここでは @val に 0 をセットしてからビューを描画します。ブラウザ画面を再描画させれば、正しい画面が見られます。

phoenix1.png

では、上で作ったボタンを見てみましょう。phx-click 属性がボタンについてるのが分かります。この属性は、クリックイベントを LiveView サーバに送るための紐付けを作成します。ではこれらのイベントを処理する関数を作りましょう。

def handle_event("inc", _, socket) do

{:noreply, update(socket, :val, &(&1 + 1))}
end

def handle_event("dec", _, socket) do
{:noreply, update(socket, :val, &(&1 - 1))}
end

ブラウザで +- のボタンを使ってみてください。で、これがどのように動いているのか説明したいところですが、まずはとにかく

おめでとうございます!

あなたは LiveView のアプリケーションを作成して、そしてそれが動いてるってことです。🎉

GIF movie of the counter

handle_event 関数の第1引数に来る値の "inc""dec"phx-click 属性から供給されます。今回は無視してる第2引数は phx-value 属性からの値が来るので、ボタンにこの属性を加えることで増減を1ではない別の値にするといったこともできます。6


LiveEEx テンプレート

では実際に動くアプリケーションができたところで LiveEEx テンプレートを使ってリファクタリングしてみましょう。ちょうど今、私たちの作ったコードはビュー上にあります。lib/live_view_counter_web/templates/counter/index.html.leex というファイルを作成することからはじめます。この新しい .leex ファイルに、HTMLコードを render 関数からコピペします。

<div>

<h1 phx-click="boom">The count is: <%= @val %></h1>
<button phx-click="dec">-</button>
<button phx-click="inc">+</button>
</div>

そして lib/live_view_counter_web/views/counter_view.ex ファイルに描画用の新しいビューを作成します。これで大変スッキリした記述になります。

defmodule LiveViewCounterWeb.CounterView do

use LiveViewCounterWeb, :view
end

そして新しいビューとテンプレートを使うように lib/live_view_counter_web/live/counter_live.ex ファイルを変更します。

defmodule LiveViewCounterWeb.CounterLive do

use Phoenix.LiveView
alias LiveViewCounterWeb.CounterView

def render(assigns) do
CounterView.render("index.html", assigns)
end
...
end

ということで、テンプレートを使う描画ができて、ちょっとコードもキレイになりました! 🎉


次のステップ

Phoenix LiveView は、フロントエンドの Javascript コードを触る必要のないようなグッと来る手法を提供します。まだ初期段階ですので、製品に応用する前にはちょっと考えないとならないところもあります。とはいえ私はこのAPIが実に気に入ってます。多分、さらにデータベースを用いるようなチュートリアルも作ると思います。LiveView で何ができるのかもっと知りたいようでしたら LiveView の例題 をチェックするのをお勧めします。

もし LiveView を使ってなにか素敵なアプリケーションを構築したら是非教えてください。みなさんがどんな作品を作るのか楽しみです。この文章や動画についてコメントがあればフィードバックをください。Twitter では dnsbty で見つかりますし、動画に直接コメントしてくれても構いません。今後も動画を作り続ける予定ですので、ご興味があれば動画に「イイね」してくださるか、私のチャンネルを視聴してくれれば新作の御案内が YouTube から行くようになります。

ここまで読んでいただいてありがとうございました!


著作権 (Copyright notice)

The original document: Copyright © 2019 Dennis Beatty

The translation: Copyright © 2019 KIKUCHI Yutaka


その他の参考文献

以下は元の文書にはない情報です。


fukuoka.ex より

私の所属しているコミュニティ fukuoka.ex の代表をつとめる piacere さんによるチュートリアルもあります。併せてご覧ください。

- LiveViewを試す①: Phoenixでリアルタイム入力/通信/反映するSPAをAPI無で実現

- LiveViewを試す②: API無しQiita検索SPAをフォームsubmitスタイルに換装

- LiveViewを試す③: LiveView用のHTMLをテンプレートファイルに分離


akiba.ex より

akiba.ex @nunulk さんの pubsub を使った例がこちらです。

Phoenix LiveView と PubSub を組み合わせてリアルタイムにクライアントを更新する





  1. 訳注:ここでいう「この金曜日」とは 2019年3月15日(金)を指しています。 



  2. 訳注:必ず自分で生成して他人にはみられないようにして置いてください。 



  3. 訳注:LiveView に view という概念があって、それが動的にどんどん描画されるライブな状態を合わせて live view と書いているようなんですが、適当な訳語が思いつかないので「ライブビュー」と訳します。 



  4. 訳注 (2019.03.24) 私の環境では脆弱性の warning が出て「こうせよ」と指示が出ました。これ従わなくても従ってもチュートリアルは続行できます。 



  5. 訳注:ここではまだカウンタについては何も記述していないので Phoenix の画面が見えるだけです。 



  6. 訳注:テンプレートに <button phx-click="dec" phx-value=2>-</button> と、ライブ関数の第2引数を v として {:noreply, update(socket, :val, &(&1 - String.to_integer(v)))} と記述すると「+」ボタンを押すと2ずつ増加するようになります。