Phoenixを触ってみる その３

[前回](https://qiita.com/utonn/items/8a1187e91eddc9a549e5) の続き

https://hexdocs.pm/phoenix/routing.html#content

今回はルーティングですが、長いので分割します。

# 基本的なところ

リクエストURLのパスに対してどのコントローラ・関数を呼び出すのかを定義するのがルーティングですが
他のFW触ってるならrouter.exの中身はイメージつくと思うので、基本的な書き方はスルーします。

``` mix phx.routes ```コマンドを使うと定義されたルーティングの一覧が参照可能なので
これを使って実際にどういったルーティングになるのかを確認できるようです。

# resources

これ1つ書くだけで、8つのルーティングが登録されるっていうやつ。

``` router.ex
scope "/", HelloWeb do
  pipe_through :browser
  resources "/users", UserController
end
```
```
$ mix phx.routes
(略)
 user_path  GET     /users             HelloWeb.UserController :index
 user_path  GET     /users/:id/edit    HelloWeb.UserController :edit
 user_path  GET     /users/new         HelloWeb.UserController :new
 user_path  GET     /users/:id         HelloWeb.UserController :show
 user_path  POST    /users             HelloWeb.UserController :create
 user_path  PATCH   /users/:id         HelloWeb.UserController :update
            PUT     /users/:id         HelloWeb.UserController :update
 user_path  DELETE  /users/:id         HelloWeb.UserController :delete
 ```

一部だけとか、一部を除くっていうのも可能。

``` router.ex
resources "/users", UserController ,only: [:index, :show]
resources "/comments", CommentController, except: [:delete]
```
```
$ mix phx.routes
(略)
   user_path  GET     /users              HelloWeb.UserController :index
   user_path  GET     /users/:id          HelloWeb.UserController :show
comment_path  GET     /comments           HelloWeb.CommentController :index
comment_path  GET     /comments/:id/edit  HelloWeb.CommentController :edit
comment_path  GET     /comments/new       HelloWeb.CommentController :new
comment_path  GET     /comments/:id       HelloWeb.CommentController :show
comment_path  POST    /comments           HelloWeb.CommentController :create
comment_path  PATCH   /comments/:id       HelloWeb.CommentController :update
              PUT     /comments/:id       HelloWeb.CommentController :update
```

似たようなルーティングを大量に書く必要がなくなるため、router.exがスッキリしそう。

# forward

hellowebではない別のPlugへリクエストを渡すことのよう。

といっても、全然元サイトに書いてあることがピンとこない。
複数機能を別々のrouterとして管理するのかな？
まだここではその話は出ないらしいので、あとでちゃんとわかるはずと思っておこう。

# Path Helpers

Routerに記載しているのはパス→コントローラの対応だが、
PathHelperを使うことで、パスを逆引きできる。

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

例がpageだから分かりにくいのだが、この ```page_path/2``` はPageController専用。
UserControllerへのパスが欲しければ ```user_path/2 ``` を使う。

これで画面にURLを埋め込むときにベタ書きしなくて良くなる。地味に便利。
そしてクエリもつけられる。やっぱり便利。
あとパラメータの数が違ったりするとエラーになるのも、地味に良い。

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

# NestedResources

resourceで一通りのCRUD用ルーティングを一括で設定できた。
それがネスト出来るという話。

``` routing.ex
resources "/users", UserController do [:index, :show]
  resources "/posts", PostController, only: [:index, :show, :create]
end
```
```
$ mix phx.routes
(略)
     user_path  GET    /users                     HelloWeb.UserController :index
     user_path  GET    /users/:id                 HelloWeb.UserController :show
user_post_path  GET    /users/:user_id/posts      HelloWeb.PostController :index
user_post_path  GET    /users/:user_id/posts/:id  HelloWeb.PostController :show
user_post_path  POST   /users/:user_id/posts      HelloWeb.PostController :create
```

ちょっとまて、```user_id``` ってどっから出てきた？
って思っていろいろ試したら、どうやらコントローラ名から拾ってきているっぽい。

``` router.ex
resources "/users", MemberController do [:index, :show]
  resources "/posts", PostController, only: [:index, :show, :create]
end
```
```
$ mix phx.routes
(略)
     member_path  GET    /users                       HelloWeb.MemberController :index
     member_path  GET    /users/:id                   HelloWeb.MemberController :show
member_post_path  GET    /users/:member_id/posts      HelloWeb.PostController :index
member_post_path  GET    /users/:member_id/posts/:id  HelloWeb.PostController :show
member_post_path  POST   /users/:member_id/posts      HelloWeb.PostController :create
```

コントローラ名に合わせて一番左の列とパス内のパラメータ名が変わっている。
PathHelperの関数名も、ネストしたものを"\_"で並べた感じになる。
というか一番左の列がそのままPathHelperの関数名として使えるっぽい。

```
iex(7)> HelloWeb.Router.Helpers.user_post_path(HelloWeb.Endpoint, :index, 1)
"/users/1/posts"
```

# Scoped Routes

名前空間を分けたいけど、リソースがネストしてるわけではない場合に使うのかな。

``` router.ex
scope "/admin" do
  resources "/reviews", HelloWeb.Admin.ReviewController
end
```

リソースのネストと違ってpath_helperのネーミングは階層が深くならない。
そのため、そのままだと他のスコープと被る可能性が出て来る

``` router.ex
scope "/", HelloWeb do
  pipe_through :browser # Use the default browser stack
  resources "/reviews", ReviewController, only: [:index,:show]
end
scope "/admin" do
  resources "/reviews", HelloWeb.Admin.ReviewController, only: [:index,:show,:delete]
end
```
```
$ mix phx.routes
(略)
review_path  GET     /reviews                   HelloWeb.ReviewController :index
review_path  GET     /reviews/:id               HelloWeb.ReviewController :show
review_path  GET     /admin/reviews             HelloWeb.Admin.ReviewController :index
review_path  GET     /admin/reviews/:id         HelloWeb.Admin.ReviewController :show
review_path  DELETE  /admin/reviews/:id         HelloWeb.Admin.ReviewController :delete
# ↑ 一番左の列が全部一緒
```

特にエラーは出なかったものの、path_helperで困る。
``` review_path ``` を使うと /reviewが取れてしまうため、/admin/reviewsに対応するpath_helperが無いことになる。

なので、その時はasを使うことで階層化出来る。

``` router.ex
scope "/", HelloWeb do
  pipe_through :browser # Use the default browser stack
  resources "/reviews", ReviewController, only: [:index,:show]
end
scope "/admin", as: :admin do
  resources "/reviews", HelloWeb.Admin.ReviewController, only: [:index,:show,:delete]
end
```
```
$ mix phx.routes
(略)
review_path  GET     /reviews                   HelloWeb.ReviewController :index
review_path  GET     /reviews/:id               HelloWeb.ReviewController :show
admin_review_path  GET     /admin/reviews             HelloWeb.Admin.ReviewController :index
admin_review_path  GET     /admin/reviews/:id         HelloWeb.Admin.ReviewController :show
admin_review_path  DELETE  /admin/reviews/:id         HelloWeb.Admin.ReviewController :delete
```
```
iex(1)> HelloWeb.Router.Helpers.review_path(HelloWeb.Endpoint, :index)
"/reviews"
iex(2)> HelloWeb.Router.Helpers.admin_review_path(HelloWeb.Endpoint, :index)
"/admin/reviews"
```

また、スコープのネストも可能。
ではあるが、複雑になるので推奨はしていないっぽい。

あと面白いのが、同一パスへのスコープを複数定義することも可能という点。
HelloWebじゃないものを横に置く場合とかに使うっぽい。
あとたぶんpipe_throughも変えられるから、 :api とかを横に置きたいときも別スコープにするのかな。

そのpipe_throughについては、次回。