はじめに
前回、簡単なアクションを追加してみました。
今回も、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
と同様のマクロに、post
、put
、patch
、delete
、options
、connect
、trace
、head
があります。
ルーティング表示
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については後述します。