/var/log/messages

Apr 16, 2019 - 2 minute read - Comments - programming

phoenix excersize (9)

継続は 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")

と書いても機能的には同等、とのこと。

ゲゲゲの女房 phoenix excersize (10)

comments powered by Disqus