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

# 訳者から読者への注

すでにこの記事の内容は古くなっています。この注を書いている時点で [LiveView](https://hex.pm/packages/phoenix_live_view) のバージョンは 0.14.7 です。この記事が書かれたときは出たばかりのバージョン 0.0.x でしたので、当時に比較して内容が相当変わっています。あえてこの記事を読む場合は、歴史的な参考書として読んでください。

## 原文について
この記事は [*Dennis Beatty*](https://twitter.com/dnsbty) さんの書いた [*How to Create a Counter with Phoenix LiveView* (Mar 19, 2019)](https://dennisbeatty.com/2019/03/19/how-to-create-a-counter-with-phoenix-live-view.html) を御本人の許可を得て日本語に翻訳した文書です。

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

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

## 編集履歴

- 2019.03.22 公開
- 2019.03.24 mix.exs に対して編集が必要な旨、npm の警告への対応、を追加
- 2019.03.25 参考文献として [@piacere_ex](https://qiita.com/piacere_ex) さんのチュートリアルの2番目を1番目とまとめて最後においた
- 2019.03.26 @__hage さんの指摘で2箇所の間違いを修正
- 2019.03.31 [@piacere_ex](https://qiita.com/piacere_ex) さんのチュートリアルの3番目を追加
- 2019.06.22 より検索で分かりやすいように、タイトルに「Elixir 入門者向け」を追加
- 2019.08.04 @nunulk さんより mix.exs に手を加える必要がなくなったとの情報を頂いて該当部を修正
- 2019.08.06 @nunulk さんより dev.exs と config.exs に対する編集が不要になったとの情報を頂いて該当部を修正
- 2020.10.17 記事の内容がすでに古くなっていることを訳注として先頭に記載。

----
# はじめに

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

[^1]: 訳注：ここでいう「この金曜日」とは 2019年3月15日（金）を指しています。

# プロジェクトの生成
まず最初に `live_view_counter` という Phoenix を基にする Elixir プロジェクトを作りましょう。それには [Phoenix generator](https://hexdocs.pm/phoenix/installation.html) を使います。今回はデータベースを使わないので Ecto なしで作りましょう。 

```
mix phx.new live_view_counter --no-ecto
```

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

```exs
{: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]

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

```exs
config :live_view_counter, LiveViewCounterWeb.Endpoint,
  ...
  live_view: [
    signing_salt: "7HekGYwxATz33gM/rH9q2mV+uKJq5/Hu"
  ]
```

翻訳者注：以下の修正は不要になったとの指摘を @nunulk さんよりもらいました。2019.08.06
~~次の設定では、拡張子が `.leex` であるようなレンダリングファイル用の LiveView テンプレートエンジンを使うことを  Phoenix に伝えます。以下の2行を追加してください。~~

```exs
config :phoenix,
  template_engines: [leex: Phoenix.LiveView.Engine]
```

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

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

```exs
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 の関数をインポートするように書き換えてみます。

```exs
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) の記述を入れましょう。

```exs
defmodule LiveViewCounterWeb.Endpoint do
  use Phoenix.Endpoint, otp_app: :live_view_counter

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

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

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

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

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

```js
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 テンプレートのリロードを連続的にできるようになります。~~

```exs
config :live_view_counter, LiveViewCounterWeb.Endpoint,
  live_reload: [
    patterns: [
      ...,
      ~r{lib/live_view_counter_web/live/.*(ex)$}
    ]
  ]
```

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

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

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

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

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

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

![Just after the making of the boilerplate](https://qiita-image-store.s3.amazonaws.com/0/260320/23611e09-b165-26fc-f0fc-7f5542d58fbe.png)

##訳注追加 (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 ルートに置き換えることからはじめます。

```exs
...
scope "/", LiveViewCounterWeb do
  pipe_through :browser

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

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

```exs
defmodule LiveViewCounterWeb.CounterLive do
  use Phoenix.LiveView
end
```

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

```exs
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 テンプレートを定義する新しい文法です。ここまでやると端末画面に例外が発生してるのが見られます。

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

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

```exs
def mount(_session, socket) do
  {:ok, assign(socket, :val, 0)}
end
```

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

![phoenix1.png](https://qiita-image-store.s3.amazonaws.com/0/260320/bca560a7-630c-2e41-e324-0cfdce94aa52.png)

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

```exs
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](https://dennisbeatty.com/content/2019-03-19-how-to-create-a-counter-with-phoenix-live-view/counter.gif)

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

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

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

```exs
<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` ファイルに描画用の新しいビューを作成します。これで大変スッキリした記述になります。

```exs
defmodule LiveViewCounterWeb.CounterView do
  use LiveViewCounterWeb, :view
end
```

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

```exs
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 の例題](https://github.com/chrismccord/phoenix_live_view_example) をチェックするのをお勧めします。

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

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

# 著作権 (Copyright notice)
The original document: Copyright © 2019 Dennis Beatty
The translation: Copyright © 2019 KIKUCHI Yutaka

# その他の参考文献
以下は元の文書にはない情報です。

## fukuoka.ex より
私の所属しているコミュニティ [fukuoka.ex](https://fukuokaex.fun/) の代表をつとめる [piacere](https://twitter.com/piacere_ex) さんによるチュートリアルもあります。併せてご覧ください。
- [LiveViewを試す①: Phoenixでリアルタイム入力／通信／反映するSPAをAPI無で実現](https://qiita.com/piacere_ex/items/3f8ee18c9443d63955bf) 
- [LiveViewを試す②: API無しQiita検索SPAをフォームsubmitスタイルに換装](https://qiita.com/piacere_ex/items/21b0e308a36e486d8b25)
- [LiveViewを試す③: LiveView用のHTMLをテンプレートファイルに分離](https://qiita.com/piacere_ex/items/5b1aabb0c45bb9934bb9)

## akiba.ex より
akiba.ex @nunulk さんの pubsub を使った例がこちらです。
[Phoenix LiveView と PubSub を組み合わせてリアルタイムにクライアントを更新する](https://qiita.com/nunulk/items/e7dfc37299659a037700#_reference-10f0c605278a0432ff42)