Elixir
Phoenix

Phoenixガイドラインを読んでみた(3) - Routing -

More than 1 year has passed since last update.


はじめに

前回、簡単なアクションを追加してみました。

今回も、Phoenix公式ドキュメントに沿って見ていきます。

なお、次回は、ガイドラインのPlugです。


航路を決めている


router.ex

Routerは、HTTPリクエストとActionとのマッチング、Channelの制御、Pipelineの定義、RouteへのPipelineセットなどを行います。

Phoenixが生成する初期状態のweb/router.exは、以下の通りです。

defmodule HelloPhoenix.Router do

use HelloPhoenix.Web, :router

pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_flash
plug :protect_from_forgery
plug :put_secure_browser_headers
end

pipeline :api do
plug :accepts, ["json"]
end

scope "/", HelloPhoenix do
pipe_through :browser # Use the default browser stack

get "/", PageController, :index
end

# Other scopes may use custom stacks.
# scope "/api", HelloPhoenix do
# pipe_through :api
# end
end

use HelloPhoenix.Web, :routerは、web/web.exのrounter関数を呼び出して、Phoenixのrounter用の処理を行います。

Scope(scope "/", HelloPhoenix do ... end)については、このテキスト内のScopeについての説明で後述します。

pipe_through :browserについても、同様にテキスト内のPipelineについての説明で後述します。

scope内ではHTTPメソッドとパス、Controller、Actionでルーティングを記述しますが、getはマクロになっていて、以下のような関数定義に展開されます。なお、関数の中身は、Controller、Actionを呼び出すようになっています。

def match(conn, "GET", ["/"])

getと同様のマクロに、postputpatchdeleteoptionsconnecttraceheadがあります。


ルーティング表示

mix phoenix.routesを使うことでルーティングを表示することができます(ただし、ビルドする前には表示できません)。

前回、追加したget "/hello"get "/hello/:messenger/"がある状態で、mix phoenix.routesを実行してみます。

 page_path  GET  /                  HelloPhoenix.PageController :index

hello_path GET /hello HelloPhoenix.HelloController :index
hello_path GET /hello/:messenger HelloPhoenix.HelloController :show


Resource (RESTが便利)

HTTPメソッドget、post、putなどを書く代わりに8個のmatch関数を展開してくれるresourcesマクロがあります。

例えば、web/routes.exに、resourcesマクロを追加してみます。

scope "/", HelloPhoenix do

pipe_through :browser # Use the default browser stack

get "/", PageController, :index
resources "/users", UserController
end

そして、$ mix phoenix.routesを実行してみると、resourcesマクロに対応した部分は、以下のようになります。

 user_path  GET     /users             HelloPhoenix.UserController :index

user_path GET /users/:id/edit HelloPhoenix.UserController :edit
user_path GET /users/new HelloPhoenix.UserController :new
user_path GET /users/:id HelloPhoenix.UserController :show
user_path POST /users HelloPhoenix.UserController :create
user_path PATCH /users/:id HelloPhoenix.UserController :update
PUT /users/:id HelloPhoenix.UserController :update
user_path DELETE /users/:id HelloPhoenix.UserController :delete

8個のmatch関数のうち、一部しか必要無い場合には、resourcesマクロの第三パラメータとして:onlyを追加して、必要な関数のアトムを指定します。

resources "/users", UserController, only: [:index, :show]

また、必要無い関数は:exceptとして記述することもできます。

resources "/users", UserController, except: [:delete]


Path Helper

パスのヘルパ関数がモジュールHelloPhoenix.Router.Helpersモジュールに定義されています(HelloPhoenixはもちろんアプリケーションに応じて変更)。

例えば、Controller名+_pathは、指定されたActionのパスを返します。例えば、PageControllerであれば、page_pathです。

なお、第一引数のHelloPhoenix.Endpointはlib/hello_phoenix/endpoint.exで定義されています。

$ iex -S mix

iex> HelloPhoenix.Router.Helpers.page_path(HelloPhoenix.Endpoint, :index)
"/"

また、各view用の処理use HelloPhoenix.Web, :view内で、import HelloPhoenix.Router.Helpersを行っており、Template内でヘルパ関数を使うことができます。

<a href="<%= page_path(@conn, :index) %>">To the Welcome Page!</a>


もっともっとPath Helper

上記のUserControllerの場合は、user_path関数でActionのパスを返すことができます。

iex> import HelloPhoenix.Router.Helpers

iex> alias HelloPhoenix.Endpoint
iex> user_path(Endpoint, :index)
"/users"

iex> user_path(Endpoint, :show, 17)
"/users/17"

iex> user_path(Endpoint, :new)
"/users/new"

iex> user_path(Endpoint, :create)
"/users"

iex> user_path(Endpoint, :edit, 37)
"/users/37/edit"

iex> user_path(Endpoint, :update, 37)
"/users/37"

iex> user_path(Endpoint, :delete, 17)
"/users/17"

iex> user_path(Endpoint, :show, 17, admin: true, active: false)
"/users/17?admin=true&active=false"

_pathの代わりに_urlを指定するとフルパスで返します。

iex(3)> user_url(Endpoint, :index)

"http://localhost:4000/users"


階層化されたResource

resourcesマクロをネストすることもできます。

resources "/users", UserController do

resources "/posts", PostController
end

これを$ mix phoenix.routesすると、以下の行が追加されています。

user_post_path  GET     users/:user_id/posts HelloPhoenix.PostController :index

user_post_path GET users/:user_id/posts/:id/edit HelloPhoenix.PostController :edit
user_post_path GET users/:user_id/posts/new HelloPhoenix.PostController :new
user_post_path GET users/:user_id/posts/:id HelloPhoenix.PostController :show
user_post_path POST users/:user_id/posts HelloPhoenix.PostController :create
user_post_path PATCH users/:user_id/posts/:id HelloPhoenix.PostController :update
PUT users/:user_id/posts/:id HelloPhoenix.PostController :update
user_post_path DELETE users/:user_id/posts/:id HelloPhoenix.PostController :delete

Path Helperを使う場合は、以下のようになります。

iex> alias HelloPhoenix.Endpoint

iex> HelloPhoenix.Router.Helpers.user_post_path(Endpoint, :show, 42, 17)
"/users/42/posts/17"
iex> HelloPhoenix.Router.Helpers.user_post_path(Endpoint, :index, 42, active: true)
"/users/42/posts?active=true"


Scope

Scopeは、共通のパスprefixによるグループ化で、それぞれにPlugのセットを割り当てることができます。

例えば、前述のRouteに/admin下の"/users"を定義することにします。

  scope "/admin" do

resources "/users", HelloPhoenix.Admin.UserController, only: [:index]
end

これを$ mix phoenix.routesすると、以下の行が追加されています。

     user_path  GET     /admin/users                    HelloPhoenix.Admin.UserController :index

ただ、これだとPath Helperのための名前がuser_pathでかぶってしまいます。

iex> HelloPhoenix.Router.Helpers.user_path(HelloPhoenix.Endpoint, :index)

"/users"

scopeマクロを呼びたす際に、as:を指定することで独自のPath Helperを指定することができるようになります。

  scope "/admin", as: :admin do

resources "/users", HelloPhoenix.Admin.UserController, only: [:index]
end

$ mix phoenix.routesで確認します。

admin_user_path  GET     /admin/users                    HelloPhoenix.Admin.UserController :index

iexで確認してみます。

iex> HelloPhoenix.Router.Helpers.admin_user_path(HelloPhoenix.Endpoint, :index)

"/admin/users"

Scopeもネストすることが可能です。下記の例ですと、api_v1_user_pathなどでHelper関数にアクセスすることができます。

scope "/api", HelloPhoenix.Api, as: :api do

pipe_through :api

scope "/v1", V1, as: :v1 do
resources "/images", ImageController
resources "/reviews", ReviewController
resources "/users", UserController
end
end


パイプライン

scope毎に、pipe_throughを指定して仕様するPipelineを指定することができます。

Pipelineは、Plugの順列で、リクエストに関するさまざまな処理を定義します。なお、初期状態では、:browser:apiの二つのPipelineが定義されています。

scopeがネストしている場合は、外側のpipe_throughが処理されてから、内側のpipe_throughが実行されます。

また、複数のパイプラインを実行したい場合は、pipe_throughにリストで指定します(例えば、pipe_through [:browser, :review_checks, :other_stuff])。


Endpoint Plug

Endpoint Plugは、全リクエストに対して適用されるPlugで、Routerへのディスパッチの前に以下の作業を順に行います。



  • Plug.Static 静的なコンテンツを返す。loggerより前に実行されるため、静的なコンテンツを返す場合はログ出力されません。


  • Plug.Logger リクエストをログ出力する


  • Phoenix.CodeReloader 必要に応じてwebディレクトリのリロードを行う


  • Plug.Parsers urlencoded、multipart、jsonによるリクエストの場合、それをパーズする。パーズできない場合はリクエストボディはそのまま


  • Plug.MethodOverride PUT, PATCH, DELETEをPOSTリクエストとして処理


  • Plug.Head HEADをGETリクエストとして処理


  • Plug.Session セッション管理


  • Plug.Router Routerへ処理を移譲


:browser:apiパイプライン

デフォルトで:browser:apiの二つのパイプラインが定義されています。その名の通り、:browserはブラウザからのリクエスト用で、:apiはAPI用です。

:browserは、デフォルトで以下の5つのPlugから構成されています。



  • :accepts, ["html"] Acceptできるリクエストフォーマット


  • :fetch_session セッションデータ取得と、セッションによる接続


  • :fetch_flash フラッシュメッセージの取得


  • :protect_from_forgeryおよび:put_secure_browser_headers CSRF対策用

:apiは、:accepts, ["json"]だけです。


Pipelineの追加

独自のPipelineを追加する場合、標準で定義されているPipelineと同様にpipeline/2マクロを使用します。

pipeline :review_checks do

plug :ensure_authenticated_user
plug :ensure_user_owns_review
end


Channel

Channelは、ソケットを使った双方でのリアルタイムなメッセージのやり取りを行います。

Channelについては後述します。