Phoenixで作るGPS Logging System 2 CRUDの中身 part1

# はじめに
[ひとりLiveView Advent Calendar](https://qiita.com/advent-calendar/2021/liveview_alone) の２日目の記事です

この記事は[Elixir Conf US 2021](https://www.elixirconf.com/)の[発表](https://www.youtube.com/watch?v=vHNqZmLLTj4)したシステムの構築と関連技術の解説を目的とした記事です

今回は以下の４つの解説します

- phx.gen.live で作成されたもの
- LiveViewのRouting
- handle_params
- handle_event

# phx.gen.live で作成されたもの
以下は `phx.gen.live`を実行したときの端末ログです

```bash
* creating lib/live_logger_web/live/map_live/show.ex
* creating lib/live_logger_web/live/map_live/index.ex
* creating lib/live_logger_web/live/map_live/form_component.ex
* creating lib/live_logger_web/live/map_live/form_component.html.heex
* creating lib/live_logger_web/live/map_live/index.html.heex
* creating lib/live_logger_web/live/map_live/show.html.heex
* creating test/live_logger_web/live/map_live_test.exs
* creating lib/live_logger_web/live/modal_component.ex
* creating lib/live_logger_web/live/live_helpers.ex
* creating lib/live_logger/loggers/map.ex
* creating priv/repo/migrations/20211118095356_create_maps.exs
* creating lib/live_logger/loggers.ex
* injecting lib/live_logger/loggers.ex
* creating test/live_logger/loggers_test.exs
* injecting test/live_logger/loggers_test.exs
* creating test/support/fixtures/loggers_fixtures.ex
* injecting test/support/fixtures/loggers_fixtures.ex
* injecting lib/live_logger_web.ex

Add the live routes to your browser scope in lib/live_logger_web/router.ex:

    live "/maps", MapLive.Index, :index
    live "/maps/new", MapLive.Index, :new
    live "/maps/:id/edit", MapLive.Index, :edit

    live "/maps/:id", MapLive.Show, :show
    live "/maps/:id/show/edit", MapLive.Show, :edit

```
creatingだけを分類するとこんな感じになります

### Liveview
①一覧ページ

* creating lib/live_logger_web/live/map_live/index.ex
* creating lib/live_logger_web/live/map_live/index.html.heex

②詳細ページ
* creating lib/live_logger_web/live/map_live/show.ex
* creating lib/live_logger_web/live/map_live/show.html.heex

③モーダルで表示するform
* creating lib/live_logger_web/live/map_live/form_component.ex
* creating lib/live_logger_web/live/map_live/form_component.html.heex

④汎用modal
* creating lib/live_logger_web/live/modal_component.ex
* creating lib/live_logger_web/live/live_helpers.ex

コントローラーに当たる箇所が.exファイル、ビューに当たるのが.heexです
htmlがシンプルな場合は .ex内のrender関数内で`~H`(シジル)で囲って記述することもできます

```leex
def render(assigns) do
  ~H"""
  <h1>Title</h1>
  """
end
```

### Model
* creating priv/repo/migrations/20211118095356_create_maps.exs ①
* creating lib/live_logger/loggers/map.ex ②
* creating lib/live_logger/loggers.ex　③

①migrationファイル
②スキーマ、バリデーション周り
③クエリ類

### Test
* creating test/live_logger_web/live/map_live_test.exs
* creating test/live_logger/loggers_test.exs
* creating test/support/fixtures/loggers_fixtures.ex

テストもバックエンド、フロントエンド両方作成してくれます
本連載ではmodel,testのコードにはあまり触れずLiveViewを主に解説します

# Router
Router周りの解説をしていきます
`map_live/index.ex`を参考に見ていきましょう

```lib/live_logger_web/router.ex
defmodule LiveLoggerWeb.Router do
  ...
  scope "/", LiveLoggerWeb do
    live "/maps", MapLive.Index, :index
    live "/maps/new", MapLive.Index, :new
    live "/maps/:id/edit", MapLive.Index, :edit

    live "/maps/:id", MapLive.Show, :show
    live "/maps/:id/show/edit", MapLive.Show, :edit
  end
end
```

```lib/live_logger_web/live/map_live/index.ex
defmodule LiveLoggerWeb.MapLive.Index do
  def mount(_params, _session, socket) do
  ...
  end

  @impl true
  def handle_params(params, _url, socket) do
    {:noreply, apply_action(socket, socket.assigns.live_action, params)}
  end

  defp apply_action(socket, :edit, %{"id" => id}) do
  ...
  end

  defp apply_action(socket, :new, _params) do
  ...
  end

  defp apply_action(socket, :index, _params) do
  ...
  end
end
```

LiveViewのroutingは以下のように記述します。
SPA寄りな書き方になるで通常のCRUDとは違いますが似たように書くことができます

### live "/maps", MapLive.Index, :index
`/maps` -> url
`MapLive.Index` -> 使用するLiveView Module
`:index` -> `socket.assigns.live_action`に入るパラメーター

### live "/maps", MapLive.Index, :new
`/maps/new`にアクセスした場合に`socket.assigns.live_action`に`:new`を入れる

### live "/maps/:id/edit", MapLive.Index, :edit
`/maps/:id/edit`にアクセスした場合に`socket.assigns.live_action`に`:edit`を入れる
`:id`は以下paramsのMapのkeyになるので、
`live_action`が`:edit`でparamsに`"id"`キーを持つ以下の関数としてパターンマッチする

```elixir
  def handle_params(params, _url, socket) do
    {:noreply, apply_action(socket, socket.assigns.live_action, params)}
  end

  defp apply_action(socket, :edit, %{"id" => id}) do
  ...
  end
```

なので
`live "/maps/:id/edit", MapLive.Index, :edit`
上記の`:id`を`:hoge`に変えるとパターンマッチが失敗してエラーになり
`defp apply_action(socket, :edit, %{"hoge" => id})`
 で合わせると正常に動くようになります


# handle_params

`handle_params`はmount後に実行されるもので
`params, url, socket`を引数に処理を実行し`{:noreply, socket}`を返す関数です
今回は`apply_action`という関数を実行して live_actionを元に`:index :edit :new`に振り分けています
`apply_action`はそれぞれ以下の処理を行っています

* :index -> formを初期化, titleを`Listing Map`に設定
* :new -> formに新しいchangesetを作成, titleを`New Map`に設定
* :edit -> formにparamsのidで取得したデータをセット, titleを`Edit Map`に設定

なのでrouterの
`live "/maps", MapLive.Index, :new`を
`live "/maps", MapLive.Index, :hoge`とかにするとマッチする`apply_action`がなくエラーになります。
これに対してエラーで落とすのではなく、404ページにリダイレクトしたい場合は以下のような関数をindexの下に追加します。
一応警告は出ますが、editの上に追加すると`action, params`に関係なく実行されて他の関数にマッチしなくなるので関数の順番には注意しましょう

```elixir
defp apply_action(socket, _action, _params) do 
  push_redirect(socket, to: "/404")
end
```

CRUD画面ではURLと画面を合わせるために`handle_params`で`live_action`を元に振り分けていますが、単純なLiveViewではmount時に一覧を取得して`socket`に`assign`することが一般的です。

`handle_params`の他の用途は`maps/:id`でアクセスしたが自分が管理しているmap以外だった場合は403errorを返すとかが考えられます

# handle_event
handle_eventは`phx-click, phx-change, phx-submit`などで指定されたイベント名を引数として関数を実行します
CRUD画面では以下が実装されています

* 削除ボタンをクリック時 -> `handle_event("delete", params, socket)`
* フォームの内容が変更された時 -> `handle_event("validate", params, socket)`
* フォームがsubmitされたとき -> `handle_event("save", params, socket)`

`handle_event`内でDBへの保存や削除を行えるため新たにAPIを生やす必要がなくなります
また別の記事で解説しますが JS側から`pushEvent`関数を実行することでLiveView側の`handle_event`を実行することも可能です

# 最後に
長くなってきたので一旦ここで切ろうと思います
次はmodalに関して解説します