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

## はじめに

[前回](http://qiita.com/dynamitecoolguy/items/0a10b3ab9c248e348c3e)、簡単なアクションを追加してみました。
今回も、[Phoenix公式](http://www.phoenixframework.org/docs/routing)ドキュメントに沿って見ていきます。
なお、[次回](http://qiita.com/dynamitecoolguy/items/01df54b9eea28a0b1eb9)は、ガイドラインの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については後述します。