継続は Controllers の Redirection の項。
Redirection
- create アクションは作成したばかりの schema の show アクションにリダイレクトする
- Phoenix コントローラは
redirect/2
を提供 - Phoenix はアプリケーション内のパスへのリダイレクトと URL へのリダイレクトを区別する
リダイレクトのための route を用意して
# New route for redirects
scope "/", HelloWeb do
get "/redirect_test", PageController, :redirect_test, as: :redirect_test
end
index アクションでリダイレクト
def index(conn, _params) do
redirect(conn, to: "/redirect_test")
end
リダイレクト先のアクションも定義が必要。
def redirect_test(conn, _params) do
text(conn, "Redirect!")
end
- index にアクセスした時に発生する要求は
- ステイタスが 302 の / へのアクセス
- ステイタスが 200 の /redirect_test へのアクセス
完全修飾 URL も使えます。
def index(conn, _params) do
redirect(conn, external: "https://elixir-lang.org/")
end
パスヘルパーも使える。
def index(conn, _params) do
redirect(conn, to: Routes.redirect_test_path(conn, :redirect_test))
end
- ただし、
:to
を使うredirect/2
はパスを期待するため、URL ヘルパーは使えない- 以下は失敗する
def index(conn, _params) do
redirect(conn, external: Routes.redirect_test_url(conn, :redirect_test))
end
Action Fallback
- Action Fallback はコントローラのアクションが Plug.Conn.t を戻さない時に呼ばれる Plug のエラー処理コードを集中させることを可能にする
- アクションの戻り値とコントローラアクションに渡された conn の両方を受け取る
ええと、以下のような記述を dry に書けるのかな。
def show(conn, %{"id" => id}, current_user) do
with {:ok, post} <- Blog.fetch_post(id),
:ok <- Authorizer.authorize(current_user, :view, post) do
render(conn, "show.json", post: post)
else
{:error, :not_found} ->
conn
|> put_status(:not_found)
|> put_view(ErrorView)
|> render(:"404")
{:error, :unauthorized} ->
conn
|> put_status(403)
|> put_view(ErrorView)
|> render(:"403")
end
end
これ、アクション単位で、になるとなかなかキツい。以下なモジュール定義を書いておいて
defmodule HelloWeb.MyFallbackController do
use Phoenix.Controller
alias HelloWeb.ErrorView
def call(conn, {:error, :not_found}) do
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
end
action_fallback を使ってこれを参照するのか。
defmodule HelloWeb.MyController do
use Phoenix.Controller
alias Hello.{Authorizer, Blog}
action_fallback HelloWeb.MyFallbackController
def show(conn, %{"id" => id}, current_user) do
with {:ok, post} <- Blog.fetch_post(id),
:ok <- Authorizer.authorize(current_user, :view, post) do
render(conn, "show.json", post: post)
end
end
end
Halting the Plug Pipeline
Controllers の節、これで終了な模様。長かった。題名的に Plug Pipeline の中断、なのかな。
- リダイレクトによって Plug Pipeline は処理が停止させられる
Plug.Conn.t
には halted キーがあり、これが true になったら下流の Plug はスキップされるPlug.Conn.halt/1
を使って簡単にできる
例えば以下なエラー処理で以降の Plug を処理したくない場合、
defmodule HelloWeb.PostFinder do
use Plug
import Plug.Conn
alias Hello.Blog
def init(opts), do: opts
def call(conn, _) do
case Blog.get_post(conn.params["id"]) do
{:ok, post} ->
assign(conn, :post, post)
{:error, :notfound} ->
conn
|> send_resp(404, "Not found")
end
end
end
こう書けば中断できる模様。
case Blog.get_post(conn.params["id"]) do
{:ok, post} ->
assign(conn, :post, post)
{:error, :notfound} ->
conn
|> send_resp(404, "Not found")
|> halt()
end
ちなみに halt/1
は Plug.Conn.t の :halted
を true にするだけなので
conn
|> halt()
|> send_resp(404, "Not found")
と書いても機能的には同等、とのこと。