今更聞けないLiveView

## はじめに

この記事は、[Elixir Advent Calendar 2022](https://qiita.com/advent-calendar/2022/elixir) の 20日目の投稿です。

前日は　@a_utsuki さんの　[Elixirでつくりたいもの](https://qiita.com/a_utsuki/items/dfed8d91bb094e23154b)　です。

業務ほぼRailsの為、すこしやっては忘れ去るLiveView。以前Apiからの操作で苦労したものを、DBのデーターでやってみようという趣旨です。

## 前提

```
erlang 25.2
elixir 1.14.2-otp-25
phoenix 1.6.15
postgres 14.1
```

## アプリの作成

早速作っていきましょう。

```zsh:zsh
kimny@kimuratomoakinoMacBook my_work % mix archive.install hex phx_new
Could not find Hex, which is needed to build dependency :phx_new
Shall I install Hex? (if running non-interactively, use "mix local.hex --force") [Yn] Y
* creating /Users/kimny/.asdf/installs/elixir/1.14.2-otp-25/.mix/archives/hex-2.0.0
Resolving Hex dependencies...
Resolution completed in 0.06s
New:
  phx_new 1.6.15
* Getting phx_new (Hex package)
All dependencies are up to date
Compiling 11 files (.ex)
Generated phx_new app
(略)
* creating live_test/assets/js/app.js
* creating live_test/priv/static/robots.txt
* creating live_test/priv/static/images/phoenix.png
* creating live_test/priv/static/favicon.ico

Fetch and install dependencies? [Yn] Y
* running mix deps.get

We are almost there! The following steps are missing:

    $ cd live_test

Then configure your database in config/dev.exs and run:

    $ mix ecto.create

Start your Phoenix app with:

    $ mix phx.server

You can also run your app inside IEx (Interactive Elixir) as:

    $ iex -S mix phx.server
```

ガイドが出てますので、そのとおりに進めていきましょう。

```zsh:zsh
% mix ecto.create

13:34:15.435 [info] Compiling file system watcher for Mac...

13:34:18.906 [info] Done.
==> file_system
Compiling 7 files (.ex)
Generated file_system app
==> connection
Compiling 1 file (.ex)
Generated connection app
==> decimal
Compiling 4 files (.ex)
Generated decimal app
==> mime
Compiling 1 file (.ex)
Generated mime app
==> live_test
Could not find "rebar3", which is needed to build dependency :telemetry
I can install a local copy which is just used by Mix
略
  (mix 1.14.2) lib/mix/cli.ex:84: Mix.CLI.run_task/2
  (elixir 1.14.2) src/elixir_compiler.erl:66: :elixir_compiler.dispatch/4
  (elixir 1.14.2) src/elixir_compiler.erl:51: :elixir_compiler.compile/3

==> live_test
Compiling 14 files (.ex)
Generated live_test app
The database for LiveTest.Repo has been created
```

```zsh:zsh
% mix phx.server
[info] Running LiveTestWeb.Endpoint with cowboy 2.9.0 at 127.0.0.1:4000 (http)
[info] Access LiveTestWeb.Endpoint at http://localhost:4000
[watch] build finished, watching for changes...
```

![](https://gyazo.com/f04d9432d5b9d912d6779d527480c0f9.png)

できました。

一旦 `ctrl` + `c` で落とします。

## モデル

続いてモデルを作成します。

`Place` モデルには、 `:name, :latitude, :longitude` のattributeを作っておきます。

`Phoenix` 版の `rails g scaffold` 的なやつで作ります。

```zsh:zsh
% mix phx.gen.html Prefectures Place places name latitude:float longitude:float 
* creating lib/live_test_web/controllers/place_controller.ex
* creating lib/live_test_web/templates/place/edit.html.heex
* creating lib/live_test_web/templates/place/form.html.heex
* creating lib/live_test_web/templates/place/index.html.heex
* creating lib/live_test_web/templates/place/new.html.heex
* creating lib/live_test_web/templates/place/show.html.heex
* creating lib/live_test_web/views/place_view.ex
* creating test/live_test_web/controllers/place_controller_test.exs
* creating lib/live_test/prefectures/place.ex
* creating priv/repo/migrations/20221220093711_create_places.exs
* creating lib/live_test/prefectures.ex
* injecting lib/live_test/prefectures.ex
* creating test/live_test/prefectures_test.exs
* injecting test/live_test/prefectures_test.exs
* creating test/support/fixtures/prefectures_fixtures.ex
* injecting test/support/fixtures/prefectures_fixtures.ex

Add the resource to your browser scope in lib/live_test_web/router.ex:

    resources "/places", PlaceController


Remember to update your repository by running migrations:

    $ mix ecto.migrate
```

マイグレーションを通します。

```zsh:zsh
% mix ecto.migrate
warning: the :gettext compiler is no longer required in your mix.exs.

Please find the following line in your mix.exs and remove the :gettext entry:

    compilers: [..., :gettext, ...] ++ Mix.compilers(),

  (gettext 0.20.0) lib/mix/tasks/compile.gettext.ex:5: Mix.Tasks.Compile.Gettext.run/1
  (mix 1.14.2) lib/mix/task.ex:421: anonymous fn/3 in Mix.Task.run_task/4
  (mix 1.14.2) lib/mix/tasks/compile.all.ex:92: Mix.Tasks.Compile.All.run_compiler/2
  (mix 1.14.2) lib/mix/tasks/compile.all.ex:72: Mix.Tasks.Compile.All.compile/4
  (mix 1.14.2) lib/mix/tasks/compile.all.ex:59: Mix.Tasks.Compile.All.with_logger_app/2
  (mix 1.14.2) lib/mix/tasks/compile.all.ex:33: Mix.Tasks.Compile.All.run/1


02:21:14.515 [info] == Running 20221220093711 LiveTest.Repo.Migrations.CreatePlaces.change/0 forward

02:21:14.517 [info] create table places

02:21:14.522 [info] == Migrated 20221220093711 in 0.0s

```

### warningが出る？

何やら `warning` 出てますね。
`mix.exs` にある、

```elixir:mix.exs
compilers: [..., :gettext, ...] ++ Mix.compilers(),
```

この記述を消せというようです。
ファイルはプロジェクト直下にあります。

```elixir:mix.exs

defmodule LiveTest.MixProject do
  use Mix.Project

  def project do
    [
      app: :live_test,
      version: "0.1.0",
      elixir: "~> 1.12",
      elixirc_paths: elixirc_paths(Mix.env()),
      compilers: [..., :gettext, ...] ++ Mix.compilers(),#←これを消しておきましょう。
      start_permanent: Mix.env() == :prod,
      aliases: aliases(),
      deps: deps()
    ]
  end
（略）
```

下記のようになればOKです。

```elixir:mix.exs

defmodule LiveTest.MixProject do
  use Mix.Project

  def project do
    [
      app: :live_test,
      version: "0.1.0",
      elixir: "~> 1.12",
      elixirc_paths: elixirc_paths(Mix.env()),
      start_permanent: Mix.env() == :prod,
      aliases: aliases(),
      deps: deps()
    ]
  end
（略）
```

`:gettext` 自体は必要なので、ライブラリから消さないようにとの事。
バージョンが上がって設定の書き方が変わっているようです。

## ダミーデーター

では、次に `seed` を書いてみましょう。

```elixir
alias LiveTest.Prefectures.Place
alias LiveTest.Repo

data = [
    ["東京駅", 33.8569343, 30.7466767],
    ["梅田駅", 34.7031952, 135.4276025],
    ["天神駅", 33.5913809, 130.3288387]
]

data
    |> Enum.map(fn [name, latitude, longitude] -> %Place{name: name, latitude: latitude, longitude: longitude} end)
    |> Enum.map(fn (place) -> Repo.insert!(place) end)
```

とりあえず３件程。必要な `alias` も書いておきます。
下記のようになればOKです。

ファイルを開くとコメントがありますが、コメントの下にでも追記しましょう。

```elixir:priv/repo/seeds.exs
# Script for populating the database. You can run it as:
#
#     mix run priv/repo/seeds.exs
#
# Inside the script, you can read and write to any of your
# repositories directly:
#
#     LiveTest.Repo.insert!(%LiveTest.SomeSchema{})
#
# We recommend using the bang functions (`insert!`, `update!`
# and so on) as they will fail if something goes wrong.

alias LiveTest.Prefectures.Place
alias LiveTest.Repo

data = [
    ["東京駅", 33.8569343, 30.7466767],
    ["梅田駅", 34.7031952, 135.4276025],
    ["天神駅", 33.5913809, 130.3288387]
]

data
    |> Enum.map(fn [name, latitude, longitude] -> %Place{name: name, latitude: latitude, longitude: longitude} end)
    |> Enum.map(fn (place) -> Repo.insert!(place) end)
```

コメント部分には、seedの流し方

```zsh
mix run priv/repo/seeds.exs
```

が書かれていますので、実行します。

```zsh:zsh
% mix run priv/repo/seeds.exs
[debug] QUERY OK db=2.2ms decode=1.2ms queue=0.3ms idle=25.4ms
INSERT INTO "places" ("latitude","longitude","name","inserted_at","updated_at") VALUES ($1,$2,$3,$4,$5) RETURNING "id" [33.8569343, 30.7466767, "東京駅", ~N[2022-12-20 18:30:55], ~N[2022-12-20 18:30:55]]
↳ Enum."-map/2-lists^map/1-0-"/2, at: lib/enum.ex:1658
[debug] QUERY OK db=0.3ms queue=0.4ms idle=36.5ms
INSERT INTO "places" ("latitude","longitude","name","inserted_at","updated_at") VALUES ($1,$2,$3,$4,$5) RETURNING "id" [34.7031952, 135.4276025, "梅田駅", ~N[2022-12-20 18:30:55], ~N[2022-12-20 18:30:55]]
↳ Enum."-map/2-lists^map/1-0-"/2, at: lib/enum.ex:1658
[debug] QUERY OK db=0.2ms queue=0.2ms idle=37.3ms
INSERT INTO "places" ("latitude","longitude","name","inserted_at","updated_at") VALUES ($1,$2,$3,$4,$5) RETURNING "id" [33.5913809, 130.3288387, "天神駅", ~N[2022-12-20 18:30:55], ~N[2022-12-20 18:30:55]]
↳ Enum."-map/2-lists^map/1-0-"/2, at: lib/enum.ex:1658
```

登録できました。

iexで確認したいので、一旦 `iex` 用の `alias` も書いておきます。
プロジェクト直下に`.iex.exs` を新規作成します。

```elixir:.iex.exs
alias LiveTest.Repo
alias LiveTest.Prefectures
alias LiveTest.Prefectures.Place
```

こんな感じでOKです。

では、 `iex` をPhoenixから起動しましょう。 `-S mix` を付けます。

```zsh:zsh
% iex -S mix          
Erlang/OTP 25 [erts-13.1.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit]

Interactive Elixir (1.14.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> 
```

起動しました。下記を試しておきます。

```elixir:iex
iex(1)> Place |> Repo.all
[debug] QUERY OK source="places" db=7.9ms decode=1.3ms queue=0.7ms idle=1601.5ms
SELECT p0."id", p0."latitude", p0."longitude", p0."name", p0."inserted_at", p0."updated_at" FROM "places" AS p0 []
↳ anonymous fn/4 in :elixir.eval_external_handler/1, at: src/elixir.erl:309
[
  %LiveTest.Prefectures.Place{
    __meta__: #Ecto.Schema.Metadata<:loaded, "places">,
    id: 1,
    latitude: 33.8569343,
    longitude: 30.7466767,
    name: "東京駅",
    inserted_at: ~N[2022-12-20 18:50:03],
    updated_at: ~N[2022-12-20 18:50:03]
  },
  %LiveTest.Prefectures.Place{
    __meta__: #Ecto.Schema.Metadata<:loaded, "places">,
    id: 2,
    latitude: 34.7031952,
    longitude: 135.4276025,
    name: "梅田駅",
    inserted_at: ~N[2022-12-20 18:50:03],
    updated_at: ~N[2022-12-20 18:50:03]
  },
  %LiveTest.Prefectures.Place{
    __meta__: #Ecto.Schema.Metadata<:loaded, "places">,
    id: 3,
    latitude: 33.5913809,
    longitude: 130.3288387,
    name: "天神駅",
    inserted_at: ~N[2022-12-20 18:50:03],
    updated_at: ~N[2022-12-20 18:50:03]
  }
]
```

```elixir:iex
iex(2)> Place |> Repo.get(2)
[debug] QUERY OK source="places" db=0.4ms queue=0.5ms idle=1496.1ms
SELECT p0."id", p0."latitude", p0."longitude", p0."name", p0."inserted_at", p0."updated_at" FROM "places" AS p0 WHERE (p0."id" = $1) [2]
↳ anonymous fn/4 in :elixir.eval_external_handler/1, at: src/elixir.erl:309
%LiveTest.Prefectures.Place{
  __meta__: #Ecto.Schema.Metadata<:loaded, "places">,
  id: 2,
  latitude: 34.7031952,
  longitude: 135.4276025,
  name: "梅田駅",
  inserted_at: ~N[2022-12-20 18:50:03],
  updated_at: ~N[2022-12-20 18:50:03]
}
```

## ルーティング

作られたページの為のルーティングを書き換えます。
今回は、 `gen.html` で作っていますので、 `resources` のルーティングを書く事と、トップページの設定を行います。

```elixir:lib/live_test_web/router.ex
defmodule LiveTestWeb.Router do
  use LiveTestWeb, :router

  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_live_flash
    plug :put_root_layout, {LiveTestWeb.LayoutView, :root}
    plug :protect_from_forgery
    plug :put_secure_browser_headers
  end

  pipeline :api do
    plug :accepts, ["json"]
  end

  scope "/", LiveTestWeb do
    pipe_through :browser

    get "/", PageController, :index
  end
(略)
```

このようになっていますので、

```elixir
scope "/", LiveTestWeb do
end
```

このブロック内を以下のように書き換えます。

```elixir
  scope "/", LiveTestWeb do
    pipe_through :browser

    get "/", PlaceController, :index
    resources "/places", PlaceController
  end
```

全体では、以下のようになります。

```elixir:lib/live_test_web/router.ex
defmodule LiveTestWeb.Router do
  use LiveTestWeb, :router

  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_live_flash
    plug :put_root_layout, {LiveTestWeb.LayoutView, :root}
    plug :protect_from_forgery
    plug :put_secure_browser_headers
  end

  pipeline :api do
    plug :accepts, ["json"]
  end

  scope "/", LiveTestWeb do
    pipe_through :browser

    get "/", PlaceController, :index
    resources "/places", PlaceController
  end
(略)
```

では、サーバーを起動しましょう。 `iex` を使う可能性がある場合は、

```
iex -S mix phx.server
```

で、ブラウザと iex 間でシームレスに実験が出来ます。

では、 `localhost:4000` を見てみましょう。

![](https://gyazo.com/5d5b300af352d3ce22eaedda85a10aab.png)

いい感じです。
一通り遷移できたら一度落としましょう。

## LiveViewへの移行

では、LiveViewの導入に入ります。
今回は、DBからのデーターをLiveViewで表示し、簡単なイベントを仕込んでみたいと思います。

モデル作成時のコマンドを改造します。

```elixir
mix phx.gen.live Prefectures Place places name latitude:float longitude:float
```

```zsh:zsh
% mix phx.gen.live Prefectures Place places name latitude:float longitude:float
You are generating into an existing context.

The LiveTest.Prefectures context currently has 6 functions and 1 file in its directory.

  * It's OK to have multiple resources in the same context as long as they are closely related. But if a context grows too large, consider breaking it apart

  * If they are not closely related, another context probably works better

The fact two entities are related in the database does not mean they belong to the same context.

If you are not sure, prefer creating a new context over adding to the existing one.

Would you like to proceed? [Yn] Y   
The following files conflict with new files to be generated:

  * lib/live_test_web/live/place_live/show.ex
  * lib/live_test_web/live/place_live/index.ex
  * lib/live_test_web/live/place_live/form_component.ex
  * lib/live_test_web/live/place_live/form_component.html.heex
  * lib/live_test_web/live/place_live/index.html.heex
  * lib/live_test_web/live/place_live/show.html.heex
  * test/live_test_web/live/place_live_test.exs
  * lib/live_test/prefectures/place.ex

See the --web option to namespace similarly named resources

Proceed with interactive overwrite? [Yn] Y
* creating lib/live_test_web/live/place_live/show.ex
* creating lib/live_test_web/live/place_live/index.ex
* creating lib/live_test_web/live/place_live/form_component.ex
* creating lib/live_test_web/live/place_live/form_component.html.heex
* creating lib/live_test_web/live/place_live/index.html.heex
* creating lib/live_test_web/live/place_live/show.html.heex
* creating test/live_test_web/live/place_live_test.exs
* creating lib/live_test/prefectures/place.ex
* creating priv/repo/migrations/20221220193931_create_places.exs

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

    live "/places", PlaceLive.Index, :index
    live "/places/new", PlaceLive.Index, :new
    live "/places/:id/edit", PlaceLive.Index, :edit

    live "/places/:id", PlaceLive.Show, :show
    live "/places/:id/show/edit", PlaceLive.Show, :edit


Remember to update your repository by running migrations:

    $ mix ecto.migrate
```

と上書きを聞かれますが、全て `Y` でOKです。

そして、

![](https://gyazo.com/b37cdb4242889cdef13d7c45f2bd5542.png)

同じマイグレーションファイルが出来るので、削除しておきましょう。

### ルーティングの変更点

先程作成した

```elixir
  scope "/", LiveTestWeb do
    pipe_through :browser

    get "/", PlaceController, :index
    resources "/places", PlaceController
  end
```

こちらは、以下のように書き換えます。

```elixir
  scope "/", LiveTestWeb do
    pipe_through :browser

    live "/", PlaceLive.Index, :index
    live "/places", PlaceLive.Index, :index
    live "/places/new", PlaceLive.Index, :new
    live "/places/:id/edit", PlaceLive.Index, :edit

    live "/places/:id", PlaceLive.Show, :show
    live "/places/:id/show/edit", PlaceLive.Show, :edit
  end
```

これでLiveViewへの変換が終了です。

![](https://gyazo.com/509c4aff3bc803e1a14716f7b4394152.png)

このタイミングで、以前のコントローラーや、テンプレートなども消してしまって良いです。

### コントローラー側

LiveViewは感覚的にVue.jsのような感じでイベントやステートの管理が出来ます。
そしてすでに必要なコードが書かれていますので、変更点を書き加えて行く状態になります。

例えば、

[![Image from Gyazo](https://i.gyazo.com/832502a90be590cbaa85db07dfd4d7a8.gif)](https://gyazo.com/832502a90be590cbaa85db07dfd4d7a8)

こんな感じででボタンを動かしたい場合、 `mount/3` に `socket` にステートを入れてしまいます。

```elixir
  def mount(_params, _session, socket) do
    socket = socket
      |> assign(:data,
        %{
          columns:
             %{
                name: %{ status: "asc", icon: "▲" },
                latitude: %{ status: "unsorted", icon: "■" },
                longitude: %{ status: "unsorted", icon: "■" }
             },
          default_column: :name
        }
      )
    {:ok, assign(socket, :places, list_places())}
  end
```

そして、

```elixir
  def handle_event("sort", value, socket) do
    { :noreply, update(socket, :data, &(sort_data(&1, value))) }
  end
```

このようなイベントハンドラーを書いておき、

```elixir
  def sort_data(data, value) do
    sort =  value["sort"]
      |> String.to_atom
    status =  value["status"]
      |> String.to_atom
    result = fn
      { :desc, data } ->
        _data = data
          #ここで並び替え
        dom_update(_data, sort, "asc", "▲")
      { _, data } ->
        _data = data
          #ここで並び替え
        dom_update(_data, sort, "desc", "▼")
    end
    result.({ status, data })
  end

  def dom_update(data, sort, status, icon) do
    data
      |> put_in([:columns, data[:default_column]], %{ status: "unsorted", icon: "■" })
      |> put_in([:columns, sort, :icon], icon )
      |> put_in([:columns, sort, :status], status )
      |> Map.put(:default_column, sort)
  end
```

ステートに加えた変更を更新するといった流れです。

全体では、

```elixir:lib/live_test_web/live/place_live/index.ex
defmodule LiveTestWeb.PlaceLive.Index do
  use LiveTestWeb, :live_view

  alias LiveTest.Prefectures
  alias LiveTest.Prefectures.Place

  @impl true
  def mount(_params, _session, socket) do
    socket = socket
      |> assign(:data,
        %{
          columns:
             %{
                name: %{ status: "asc", icon: "▲" },
                latitude: %{ status: "unsorted", icon: "■" },
                longitude: %{ status: "unsorted", icon: "■" }
             },
          default_column: :name
        }
      )
    {:ok, assign(socket, :places, list_places())}
  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
    socket
    |> assign(:page_title, "Edit Place")
    |> assign(:place, Prefectures.get_place!(id))
  end

  defp apply_action(socket, :new, _params) do
    socket
    |> assign(:page_title, "New Place")
    |> assign(:place, %Place{})
  end

  defp apply_action(socket, :index, _params) do
    socket
    |> assign(:page_title, "Listing Places")
    |> assign(:place, nil)
  end

  @impl true
  def handle_event("delete", %{"id" => id}, socket) do
    place = Prefectures.get_place!(id)
    {:ok, _} = Prefectures.delete_place(place)

    {:noreply, assign(socket, :places, list_places())}
  end

  defp list_places do
    Prefectures.list_places()
  end

  def handle_event("sort", value, socket) do
    { :noreply, update(socket, :data, &(sort_data(&1, value))) }
  end

  def sort_data(data, value) do
    sort =  value["sort"]
      |> String.to_atom
    status =  value["status"]
      |> String.to_atom
    result = fn
      { :desc, data } ->
        _data = data
          #ここで並び替え
        dom_update(_data, sort, "asc", "▲")
      { _, data } ->
        _data = data
          #ここで並び替え
        dom_update(_data, sort, "desc", "▼")
    end
    result.({ status, data })
  end

  def dom_update(data, sort, status, icon) do
    data
      |> put_in([:columns, data[:default_column]], %{ status: "unsorted", icon: "■" })
      |> put_in([:columns, sort, :icon], icon )
      |> put_in([:columns, sort, :status], status )
      |> Map.put(:default_column, sort)
  end

  defp place_columns do
    %{
        name:  %{ status: "asc", icon: "▲" },
        latitude: %{ status: "unsorted", icon: "■" },
        longitude: %{ status: "unsorted", icon: "■" }
      }
  end
end

```

このような感じになります。

### テンプレート側

基本的にテンプレートも自動作成されているのですが、

```erb
  <thead>
    <tr>
      <%= for column <- [:name, :latitude, :longitude] do %>
      <th
        phx-click="sort"
        phx-value-sort={ column }
        phx-value-status={ @data[:columns][column][:status] }
      >
        <%= Atom.to_string(column) %>：<%= @data[:columns][column][:icon] %>
      </th>
      <% end %>
      <th></th>
    </tr>
  </thead>
```

このように、イベントのボタンをVueライクな感じで書き加えておきます。
全体では、

```erb:lib/live_test_web/live/place_live/index.html.heex
<h1>Listing Places</h1>

<%= if @live_action in [:new, :edit] do %>
  <.modal return_to={Routes.place_index_path(@socket, :index)}>
    <.live_component
      module={LiveTestWeb.PlaceLive.FormComponent}
      id={@place.id || :new}
      title={@page_title}
      action={@live_action}
      place={@place}
      return_to={Routes.place_index_path(@socket, :index)}
    />
  </.modal>
<% end %>

<table>
  <thead>
    <tr>
      <%= for column <- [:name, :latitude, :longitude] do %>
      <th
        phx-click="sort"
        phx-value-sort={ column }
        phx-value-status={ @data[:columns][column][:status] }
      >
        <%= Atom.to_string(column) %>：<%= @data[:columns][column][:icon] %>
      </th>
      <% end %>
      <th></th>
    </tr>
  </thead>
  <tbody id="places">
    <%= for place <- @places do %>
      <tr id={"place-#{place.id}"}>
        <td><%= place.name %></td>
        <td><%= place.latitude %></td>
        <td><%= place.longitude %></td>

        <td>
          <span><%= live_redirect "Show", to: Routes.place_show_path(@socket, :show, place) %></span>
          <span><%= live_patch "Edit", to: Routes.place_index_path(@socket, :edit, place) %></span>
          <span><%= link "Delete", to: "#", phx_click: "delete", phx_value_id: place.id, data: [confirm: "Are you sure?"] %></span>
        </td>
      </tr>
    <% end %>
  </tbody>
</table>

<span><%= live_patch "New Place", to: Routes.place_index_path(@socket, :new) %></span>
```

このような感じです。

### データーの並び替え

あとはデーターを並び替えるだけです。

## おわりに

ここまで見ていただきありがとうございます。

２１日めは、

@mnishiguchi さんで [Elixirコードからmix.exsの中身を取得する方法](https://qiita.com/mnishiguchi/items/186f8c6aa280f0b085a3) です。