ElixirでTDD②- Phoenixでクエリパラメータを足し算して画面に表示する

こんにちは！
プログラミング未経験文系出身、Elixirの国に迷い込んだ？！見習いアルケミストのaliceと申します。
今回はElixirでTDDの演習として、Phoenixでクエリパラメータを渡して足し算する方法をまとめます。

# 目的
TDDの演習としてPhoenixでクエリパラメータを引数a, bとし、 a + b の実行結果を画面に表示する実装をする。

## 実行環境
Windows 11 ＋ WSL2 ＋ Ubuntu 22.04
Elixir v1.14.3
Erlang v26.0.2
Phoenix v1.7.10

# 前提
本シリーズは下記で生成されたPhoenixのプロジェクトを使用しています。
```bash:bash
mix phx.new dec19
cd dec19
mix ecto.create
```

# 事前準備
## mix test.watchの導入
TDDではテストが通る／通らないを継続的に監視する必要があるので、まずmix test.watchを導入します。
詳しくは下記で記事化しています。
以降はmix test.watchの動作確認まで済んでいる前提で進めます。

https://qiita.com/Alicesky2127/items/8aaec05b836673eab767


# ロジック部分の実装
## テストを書く
[前回の記事](https://qiita.com/Alicesky2127/items/3a75525c3cc4fc8cc24c)と同様にまず、テストを書きます。
```diff_elixir:lib/dec19.ex
 @doc """
 Get the result of adding and display.

   ## Examples
       iex> Dec19.display(1, 2)
       "add result: 3"
   """
 end
```

## (適当な実装を書いて)テストが落ちることを確認する
次に、a + bを実行するロジック部分の実装を適当に書きます。
```diff_elixir:lib/dec19.ex
   @doc """
   Get the result of adding and display.

   ## Examples
       iex> Dec19.display(1, 2)
       "add result: 3"
   """
+  def display(a, b) do
+    nil
+  end
```

Redの状態であることを確認します。
```bash:bash
Running tests...
Compiling 1 file (.ex)
.....

  1) doctest Dec19.display/2 (3) (Dec19Test)
     test/dec19_test.exs:3
     Doctest failed
     doctest:
       iex> Dec19.display(1, 2)
       "add result: 3"
     code:  Dec19.display(1, 2) === "add result: 3"
     left:  nil
     right: "add result: 3"
     stacktrace:
       lib/dec19.ex:20: Dec19 (module)


Finished in 0.08 seconds (0.03s async, 0.05s sync)
5 tests, 3 doctests, 1 failure
```
## 落ちたテストを通す最低限の実装をする
最後に、落ちたテストを通す最低限の実装をします。
```diff_elixir:lib/dec19.ex
   @doc """
   Get the result of adding and display.

   ## Examples
       iex> Dec19.display(1, 2)
       "add result: 3"
   """
   def display(a, b) do
-    nil
+    "add result: 3"
   end
 end
```
Greenの状態であることを確認します。[^1]
```bash:bash
Running tests...
Compiling 1 file (.ex)
......
Finished in 0.09 seconds (0.03s async, 0.06s sync)
5 tests, 3 doctests, 0 failures
```

# ロジック部分のリファクタリング
## テストを書く 
こちらも[前回の記事](https://qiita.com/Alicesky2127/items/3a75525c3cc4fc8cc24c)と同様にdoctestを増やしてみます。
```diff_elixir:lib/dec19.ex
   @doc """
   Get the result of adding and display.

   ## Examples
       iex> Dec19.display(1, 2)
       "add result: 3"
       
+      iex> Dec19.display(2, 3)
+     "add result: 5"
   """
   def display(a, b) do
     "add result: 3"
   end
 end
```

## テストが落ちることを確認する
再びRedの状態であることを確認します。
```bash:bash
Running tests...
Compiling 1 file (.ex)
.......

  1) doctest Dec19.display/2 (4) (Dec19Test)
     test/dec19_test.exs:3
     Doctest failed
     doctest:
       iex> Dec19.display(2, 3)
       "add result: 5"
     code:  Dec19.display(2, 3) === "add result: 5"
     left:  "add result: 3"
     right: "add result: 5"
     stacktrace:
       lib/dec19.ex:23: Dec19 (module)

..
Finished in 0.08 seconds (0.03s async, 0.05s sync)
5 tests, 4 doctests, 1 failure
```

## 落ちたテストを通す最低限の実装をする
落ちたテストを通す最低限の実装をします。
このとき、他のテストが落ちないように初めて`a + b`という変数を用いた実装になります。
```diff_elixir:lib/dec19.ex
 defmodule Dec19 do
   @doc """
   Get the result of adding and display.

   ## Examples
       iex> Dec19.display(1, 2)
       "add result: 3"
       
       iex> Dec19.display(2, 3)
      "add result: 5"
   """
   def display(a, b) do
-    "add result: 3"
+    "add result: #{a + b}"
   end
 end
```
再び、Greenの状態であることを確認します。
```bash:bash
Running tests...
Compiling 1 file (.ex)
.........
Finished in 0.08 seconds (0.03s async, 0.05s sync)
5 tests, 4 doctests, 0 failures
```
1つの実装で2つのテストを通すリファクタリングができました。

# 画面表示部分の実装
## リテラルで引数a, bを渡す
home.html.heex内でロジック部分で実装した関数displayを呼び出します。
ひとまずリテラルで引数`a, b`をそれぞれ`1, 3`として渡します。[^2]
```diff_elixir:lib/dec19_web/controllers/page_html/home.html.heex
+ <%= Dec19.display(1, 3)%>
  <.flash_group flash={@flash} />
#後略
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/677280/9f7c4a1b-8717-0f36-ddab-ef95cbe84dde.png)

## 引数a, bを変数化する
下記の仕様を叶える実装を作っていきます。
- URLが`http://localhost:4000/?a=1&b=2`だとして、引数`a, b` がそれぞれ`1, 2`になるようにしたい。
- 画面には`add result: 3`が出てほしい。
### paramsが使えるようにする
クエリパラメータ(params)を使えるように下記の通り書き換えます。
```diff_elixir:lib/dec19_web/controllers/page_controller.ex
 defmodule Dec19Web.PageController do
   use Dec19Web, :controller

-   def home(conn, _params) do
+   def home(conn, params) do
     # The home page is often custom made,
     # so skip the default app layout.
-   render(conn, :home, layout: false)
+   render(conn, :home, layout: false, params: params)
   end
 end
```

### heexファイルでparamsを使う
下記のように書き換えてparamsをロジック側に渡します。
```diff_elixir:lib/dec19_web/controllers/page_html/home.html.heex
- <%= Dec19.display(1, 3)%>
+ <%= Dec19.display(@params["a"], @params["b"])%>
  <.flash_group flash={@flash} />
#後略
```

#### もともとあったテストが落ちたので修正
もともとあった`page_controller_test.exs`のテストがクエリパラメータが無い場合にnil + nilを足そうとして落ちました。
```bash:bash
Running tests...
Compiling 1 file (.ex)
....
  1) test GET / (Dec19Web.PageControllerTest)
     test/dec19_web/controllers/page_controller_test.exs:4
     ** (ArithmeticError) bad argument in arithmetic expression: nil + nil
     code: conn = get(conn, ~p"/")
     stacktrace:
       :erlang.+(nil, nil)
       #後略
```

heexファイル側でクエリパラメータの有／無で条件分岐を書き、クエリパラメータが有るときだけparamsをロジック側に渡すようにします。

```diff_elixir:lib/dec19_web/controllers/page_html/home.html.heex
- <%= Dec19.display(@params["a"], @params["b"])%>
+ <%= if @params["a"] != nil && @params["b"] != nil, do: Dec19.display(@params["a"], @params["b"]), else: "" %>
  <.flash_group flash={@flash} />
#後略
```

条件分岐を書いた結果、`page_controller_test.exs`のテストは通るようになりましたがパラメータが文字列として渡されているようです。
```bash:bash
** (exit) an exception was raised:
    ** (ArithmeticError) bad argument in arithmetic expression
        :erlang.+("1", "2")
```

文字列ではなく数値として渡すように修正します。
```diff_elixir:lib/dec19_web/controllers/page_html/home.html.heex
- <%= if @params["a"] != nil && @params["b"] != nil, do: Dec19.display(@params["a"], @params["b"]), else: "" %>
+ <%= if @params["a"] != nil && @params["b"] != nil, do: Dec19.display(String.to_integer(@params["a"]), String.to_integer(@params["b"])), else: "" %>

  <.flash_group flash={@flash} />
#後略
```

エラーも出なくなり、かつテストも全件通るようになりました。
```bash:bash
Running tests...
Compiling 1 file (.ex)
.........
Finished in 0.06 seconds (0.02s async, 0.04s sync)
5 tests, 4 doctests, 0 failures
```

# 画面側で動作確認
1件目`http://localhost:4000/?a=1&b=2`の場合
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/677280/074f4f33-4793-2b3a-38ff-026f1f2a567c.png)

```bash:bash
[info] GET /
[debug] Processing with Dec19Web.PageController.home/2
  Parameters: %{"a" => "1", "b" => "2"}
  Pipelines: [:browser]
[info] Sent 200 in 2ms
```

2件目`http://localhost:4000/?a=1&b=5`の場合(念の為)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/677280/6194671b-2cba-514b-ed91-6dd1a96abe91.png)

```bash:bash
[info] GET /
[debug] Processing with Dec19Web.PageController.home/2
  Parameters: %{"a" => "1", "b" => "5"}
  Pipelines: [:browser]
[info] Sent 200 in 2ms
```
コンソール上でもパラメータが渡されているのが確認でき、クエリパラメータに応じて画面の表示が変化することが確認できました(^▽^)/

# リポジトリ
https://github.com/Alice2127/dec19

# 不明点
・引数a, bを変数化する背景(Phoenixのライフサイクルそのもの？)が分かっていない
・パラメータの渡し方(`@params["a"]`といった書き方)が分かっていない
・paramsってクエリパラメータ以外にも使うことなかったっけ？

# ～Elixirの国のご案内～
↓Elixirって何ぞや？と思ったらこちらもどぞ。Elixirは先端のアレコレをだいたい全部できちゃいます:laughing::sparkles::sparkles:

https://qiita.com/piacerex/items/09876caa1e17169ec5e1

↓ゼロからElixirを始めるなら「エリクサーチ」がおすすめ！私もエンジニア未経験から学習中です。

https://elixir-lang.info/topics/entry_column


↓[We Are The Alchemists, my friends!:bouquet:](https://qiita.com/torifukukaiou/items/6bcd5bbb80df9e594e9e "Awesome-san's Awesome article")[^10]
Elixirコミュニティは本当に優しくて温かい人たちばかり！
私が挫折せずにいられるのもこの恵まれた環境のおかげです。
まずは気軽にコミュニティを訪れてみてください。[^11]


https://speakerdeck.com/elijo/elixirkomiyunitei-falsebu-kifang-guo-nei-onrainbian



[^1]:Red → Greenに状態が変わったのでマイルストーンとしてgit commitしておきます。

[^2]:リテラルで画面上は表示が正しくできていることが大事。ここまで作れていればdisplay関数の出力結果を使用した後続の関数処理や画面の組み立てができる！

[^10]: @torifukukaiouさんのAwesomeな名言をお借りしました。Elixirコミュニティを一言で表すと、これに尽きます。

[^11]: @kn339264さんの素敵なスライドをお借りしました。Elixirコミュニティはいろんな形で活動中！