だいぶ間空いてしまったけど。
今日はこちら https://hexdocs.pm/phoenix/controllers.html#content
Actions
- Controller中の関数をactionと呼ぶ
- routerと名前を合わせれば良い
- actionは引数を2つ持つ
- 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に入れてるだけなので後勝ち。
def hello(conn, _params) do
conn
|> assign(:message, "hello world")
|> render("index.html", message: "hoge)
end
ちょっと横道それるけど。
こういうコード書くときに気にしたいのは、Javaとかと違ってイミュータブルなこと。
全部connを返り値としていて、それを引き継いだりactionの返り値としている。
なので、assignの返り値を無視してしまうと、値が設定されない。
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を宣言してそれぞれのエラーに対する処理を書く
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}
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は早いもの勝ち?
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
時間切れなので、いったんここまで。
続きはあとで追記する