Phoenixを触ってみる その７

だいぶ間空いてしまったけど。
今日はこちら https://hexdocs.pm/phoenix/controllers.html#content

## Actions

- Controller中の関数をactionと呼ぶ
- routerと名前を合わせれば良い
- actionは引数を２つ持つ
    - conn: リクエスト情報を持ってる。urlとか。仕組み自体はplugのもの
    - param: リクエストパラメータ

## flash message

flashMessageっていう仕組みによって、通常のパラメータとは別で通知メッセージとかが渡せる

- put_flash/3 でメッセージを渡す → viewとかでget_flashすることでそのメッセージを取得
- 中見ると、connのprivateフィールドにこの機能用のkeyで値を入れている
- バリデーションエラーとか更新成功しましたとかのメッセージで使うんかな

## Rendering

今回のメイン、レスポンスを返す部分。
いくつかレスポンス返す用の関数がある。

- text: plain textを表示するだけっていう
- json: mapをjsonにして返す。apiで使いそう
- html: htmlを返す
- render: テンプレート使って返す

htmlあるけど、普通にrender使うよね。
renderに渡したい値は、renderの第3引数に渡すか、assignを使ってconnに突っ込む。
ちなみに、単にmapに入れてるだけなので後勝ち。

```exs
  def hello(conn, _params) do
    conn
    |> assign(:message, "hello world")
    |> render("index.html", message: "hoge)
  end
```

ちょっと横道それるけど。

こういうコード書くときに気にしたいのは、Javaとかと違ってイミュータブルなこと。
全部connを返り値としていて、それを引き継いだりactionの返り値としている。
なので、assignの返り値を無視してしまうと、値が設定されない。

```exs
  def helloNG(conn, _params) do
    # 参照渡しではないので、connの中身は変更されない
    assign(conn, :message, "this message is not set")
    (略)
    render(conn, "index.html")
  end
  def helloOK(conn, _params) do
    # 書き換わったconnをその後の処理でも使う必要がある
    conn = assign(conn, :message, "this message is not set")
    (略)
    render(conn, "index.html")
  end
````

で、おもしろいのが"plug"の仕組みを使えること。
前の回で書いたように、Module共通の処理がplugとして書ける。

plugの仕組み自体、connを引き回してくようにできているので、そこで値を渡すことで
Module内のデフォルト値のようなものを設定することが可能

when を使って、対象のactionを指定することも可能

## Sending responses directly

- send_resp/2を使うことで、レスポンスコードを指定してレスポンスを返せる
- put_resp_content_type/2 使ってコンテンツタイプを指定できる

ということで、前述のhtmlとかtextってのも、実は中ではsend_respしているだけだったり。

## Assigning Layouts

layoutがあるよって話だけど、詳細はたぶん後に出てくるので
Controllerからどう扱うかってところだけ。
(あと唐突に |> 時の注意点的なの出てくるけど、ここではスルーします

- put_layout/2 で使うlayoutを指定できる。false渡すとlayout無しになる。
- パスはlayoutのあるディレクトリから相対（つまりデフォルトは"app.html")

## Overriding Rendering Formats

動的にtextかhtmlかを切り替えるよ、という話。
実際はjson or csv とかって使い方の方が多いと思うけど、、

- _formatっていうパラメータを勝手に認識してくれる
- テンプレートのディレクトリ内に、hoge.text.eexっていうのを作ると、_format=textの時にそちらが使われる
- router.exのacceptsに"text"を追加すると通る

ちょっと実験

- router.exのacceptsに"hoge"を追加
- index.hoge.eexを作成
- ?_format=hoge を付けてリクエスト

結果
なんかファイルがDLされた。中身はindex.hoge.eex。
render部分はhtml,text限らず文字列でマッチしてるだけ、
だけど、多分他のpipeとかで引っかからなくてContentsTypeとか決まらなかったんだろうな…

## Setting the Content Type

で、コンテンツタイプの指定の仕方。特に言うことなし。

## Setting the HTTP Status

- 同様に、put_status/2 でステータスを指定できる
- が、本当にステータスを変えるだけ。404とか指定しても404ページが表示されるわけではないし、302とかやってもリダイレクトしない
- なのでエラーページにするとかリダイレクトする、っていうのは別の記述を。

基本ステータスと挙動はセットなので、あまり使うことはないかな？

## Redirection

redirec/2を使うとリダイレクト出来るよって話。
第一引数がconnなのはいつもどおりとして、第二引数は使い分ける

- to: 内部でのリダイレクトの場合。パスのみ渡す。
- external: 外部へのリダイレクトの場合に使う。httpsからフルでURLを渡す・

## Action Fallback

エラーハンドリングの話。
actionFallbackって形でplugを差し込むと、actionの後にそのplugが動かせるので、それを利用する。
このfallbackなplugもControllerという扱いらしい。

- actionがエラーをreturnする
- fallbackController内でエラー毎のcallを宣言してそれぞれのエラーに対する処理を書く

``` pageController.ex
  def show(conn, %{"id" => id}) do
    with {:ok, data} <- fetch(id) do
      render(conn, "index.html", message: "hello #{data}", name: data)
    end
  end
  defp fetch("hoge"), do: {:ok, "HogeHoge"}
  defp fetch(_id), do: {:error, :not_found}
```
``` fallbackController.ex
  def call(conn, {:error, :not_found}) do
    IO.inspect "on ontfound"
    conn
    |> put_status(:not_found)
    |> put_view(ErrorView)
    |> render(:"404")
  end

  def call(conn, {:error, :unauthorized}) do
    conn
    |> put_status(403)
    |> put_view(ErrorView)
    |> render(:"403")
  end
```

エラーハンドリングの話といってみたものの、実際のところエラーかどうかは関係なくて。
単にactionの後に行う処理を書けるよ、ってことらしい。

実際はこういう横断的に使う処理って、hwllo_web.exに書いたりするのかな。
流石に全Controllerに書いてくのはおかしい気がするので。

ちなみに。
PageController側でrenderした後に再度fallback側でrenderしても何も起きなかった
renderは早いもの勝ち？

``` PageController.ex
  def show(conn, %{"id" => id}) do
    with {:ok, data} <- fetch(id) do
      render(conn, "index.html", message: "hello #{data}", name: data)
    end
    {:error, :not_found}
    # errorを返しているが、↑のrenderを通っていれば画面上は200で返る。
  end
```

---

時間切れなので、いったんここまで。
続きはあとで追記する