15
11

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-04-21

はじめに

前回、簡単なアクションを追加してみました。
今回も、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については後述します。

15
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
11