どうも、靖宗です。
前回途中で終わってるので、Routingの続きです。
まずはPath Helpers。
Path Helpers
Path Helpersとはコントローラーを作成したら勝手に出てくる関数のようです。以前mix phx.routes
をやった時に一番左にでてきたやつがPath Helpersだそうです。
PS > mix phx.routes page_path GET / HelloWeb.PageController :index
これのpage_path
ってやつですね。
コントローラーの名前から勝手に生成される関数で、ルートからのパスを返してくれるそうです。
試しにiex -S mix
で実行してみます。
PS > iex.bat -S mix Interactive Elixir (1.8.0) - press Ctrl+C to exit (type h() ENTER for help) iex(1)> HelloPhoenixWeb.Router.Helpers.page_path(HelloPhoenixWeb.Endpoint, :index) "/"
でました。HelloPhoenixWeb.Endpoint
が謎ですが、たぶん名前からしてルートの位置を示す何かなのでしょう。
この箇所にはコネクション+アクションでも良いらしいく、テンプレート上で利用するには
<a href="<%= Routes.page_path(@conn, :index) %>">To the Welcome Page!</a>
となるそうです。ここでHelloPhoenixWeb.Router.Helpers.page_path
ではなくRoutes.page_path
となっているのはPhoenixのビューが使用されている箇所(ここではレンダリングしてるとき)はHelloWeb.Router.Helpers
はRoutes
へエイリアスされているようです。
Phoenixの実装的には勝手に生成されたりするようなのであまり触らなくて良さそうですが、テンプレートを使って開発する場合には強力な機能となりそうです。
裏を返せば基本API化してしまってPhoenixにビューを担当させない場合はあまり使わないかも?
More on Path Helpers
Path Helpersはアクションへ引数を渡せるようです。
前回resources
で作成したusers
だと、下記のようになります。
iex(1)> alias HelloPhoenixWeb.Router.Helpers, as: Routes HelloPhoenixWeb.Router.Helpers iex(2)> alias HelloPhoenixWeb.Endpoint HelloPhoenixWeb.Endpoint iex(3)> Routes.user_path(Endpoint, :index) "/users" iex(4)> Routes.user_path(Endpoint, :show, 17) "/users/17" iex(5)> Routes.user_path(Endpoint, :new) "/users/new" iex(6)> Routes.user_path(Endpoint, :create) "/users" iex(7)> Routes.user_path(Endpoint, :edit, 37) "/users/37/edit" iex(8)> Routes.user_path(Endpoint, :update, 37) "/users/37" iex(9)> Routes.user_path(Endpoint, :delete, 17) "/users/17"
URLパラメーターも渡せます。キーバリューのペアで渡すとクエリ文字列に変換されるようです。
iex(10)> Routes.user_path(Endpoint, :show, 17, admin: true, active: false) "/users/17?admin=true&active=false"
ルートからの相対パスではなく絶対パスも取得できるそうです。user_path
のpathをurlへ変更します。
iex(11)> Routes.user_url(Endpoint, :index) "http://localhost:4000/users"
この絶対パスはconfig/dev.exs
のデータから生成されているようです。
Nested Resources
前回やったresources
、ネストもできるみたいです。
基本的にresources
って言うぐらいなので複数の物が出てくる(複数のユーザーがでてくる、みたいな)想定なのですが、コレをネストするということはすなわち多対一の関係になります。
例えば、Twitterなどのユーザーがポストを投稿できるときなんかは下記の通りになります。
resources "/users", UserController do resources "/posts", PostController end
1個のユーザーに対して複数のポストがある事になります。
このときのmix phx.routes
は以下のよう。
page_path GET / HelloPhoenixWeb.PageController :index user_path GET /users HelloPhoenixWeb.UserController :index user_path GET /users/:id/edit HelloPhoenixWeb.UserController :edit user_path GET /users/new HelloPhoenixWeb.UserController :new user_path GET /users/:id HelloPhoenixWeb.UserController :show user_path POST /users HelloPhoenixWeb.UserController :create user_path PATCH /users/:id HelloPhoenixWeb.UserController :update PUT /users/:id HelloPhoenixWeb.UserController :update user_path DELETE /users/:id HelloPhoenixWeb.UserController :delete user_post_path GET /users/:user_id/posts HelloPhoenixWeb.PostController :index user_post_path GET /users/:user_id/posts/:id/edit HelloPhoenixWeb.PostController :edit user_post_path GET /users/:user_id/posts/new HelloPhoenixWeb.PostController :new user_post_path GET /users/:user_id/posts/:id HelloPhoenixWeb.PostController :show user_post_path POST /users/:user_id/posts HelloPhoenixWeb.PostController :create user_post_path PATCH /users/:user_id/posts/:id HelloPhoenixWeb.PostController :update PUT /users/:user_id/posts/:id HelloPhoenixWeb.PostController :update user_post_path DELETE /users/:user_id/posts/:id HelloPhoenixWeb.PostController :delete
ここまでできれば下手するとモデリング無しで簡単なシステムが作れそうです。
ただし、全ポストの表示などは別の処理が要りそうです。
先ほどのPath Helpersもネスとしてもいけます。ただし各ユーザーにポストがぶら下がってるので、ポストの表示はユーザーIDを指定してやる必要があるのでその辺は要注意です。
iex> alias HelloWeb.Endpoint iex> HelloWeb.Router.Helpers.user_post_path(Endpoint, :show, 42, 17) "/users/42/posts/17"
URLパラメータも同様。
iex> HelloWeb.Router.Helpers.user_post_path(Endpoint, :index, 42, active: true) "/users/42/posts?active=true"
Scoped Routes
とりあえずよくもわからずscope "/", HelloPhoenixWeb do
とか書いてたあたりの説明です。
どうやらこのscope
というマクロは共通パスをまとめる機能だそうです。"/"
のスコープは全て共通ということですね。
とりあえずこの項目ではreviews
というresource
を想定して進めて行きます。なので、reviews
のパスは
/reviews /reviews/1234 /reviews/1234/edit ...
ということになります。
ここで、このreviews
を管理者権限を持つ物が特別な挙動でいじくりまわす想定をすると
/admin/reviews /admin/reviews/1234 /admin/reviews/1234/edit ...
みたいになってると嬉しいです。
ここでscope
の登場です。router.ex
を次のように変更してみます。
... scope "/", HelloPhoenixWeb do pipe_through :browser get "/", PageController, :index resources "/users", UserController do resources "/posts", PostController end resources "/reviews", ReviewController end scope "/admin" do pipe_through :browser resources "/reviews", HelloPhoenixWeb.Admin.ReviewController end ...
このとき、mix phx.routes
を実行すると
review_path GET /reviews HelloPhoenixWeb.HelloPhoenixWeb.ReviewController :index review_path GET /reviews/:id/edit HelloPhoenixWeb.HelloPhoenixWeb.ReviewController :edit review_path GET /reviews/new HelloPhoenixWeb.HelloPhoenixWeb.ReviewController :new review_path GET /reviews/:id HelloPhoenixWeb.HelloPhoenixWeb.ReviewController :show review_path POST /reviews HelloPhoenixWeb.HelloPhoenixWeb.ReviewController :create review_path PATCH /reviews/:id HelloPhoenixWeb.HelloPhoenixWeb.ReviewController :update PUT /reviews/:id HelloPhoenixWeb.HelloPhoenixWeb.ReviewController :update review_path DELETE /reviews/:id HelloPhoenixWeb.HelloPhoenixWeb.ReviewController :delete review_path GET /admin/reviews HelloPhoenixWeb.Admin.ReviewController :index review_path GET /admin/reviews/:id/edit HelloPhoenixWeb.Admin.ReviewController :edit review_path GET /admin/reviews/new HelloPhoenixWeb.Admin.ReviewController :new review_path GET /admin/reviews/:id HelloPhoenixWeb.Admin.ReviewController :show review_path POST /admin/reviews HelloPhoenixWeb.Admin.ReviewController :create review_path PATCH /admin/reviews/:id HelloPhoenixWeb.Admin.ReviewController :update PUT /admin/reviews/:id HelloPhoenixWeb.Admin.ReviewController :update review_path DELETE /admin/reviews/:id HelloPhoenixWeb.Admin.ReviewController :delete
きちんと"/admin"
が着いた形でパスが返ってきてます。
ちなみにサンプルではscope "/admin" do
になってますが
scope "/admin", HelloPhoenixWeb do pipe_through :browser resources "/reviews", Admin.ReviewController end
でも同様になりました。
ただし現状では問題が。
よくよく見てみると普通の方もadminの方も同じreview_path
というpath helperになっています。これはビューのレンダリングとかでマズい気がします。
ここで、scope
の箇所をscope "/admin", as: :admin do
とすることでpath helperの重複を回避できます。
review_path GET /reviews HelloPhoenixWeb.ReviewController :index review_path GET /reviews/:id/edit HelloPhoenixWeb.ReviewController :edit review_path GET /reviews/new HelloPhoenixWeb.ReviewController :new review_path GET /reviews/:id HelloPhoenixWeb.ReviewController :show review_path POST /reviews HelloPhoenixWeb.ReviewController :create review_path PATCH /reviews/:id HelloPhoenixWeb.ReviewController :update PUT /reviews/:id HelloPhoenixWeb.ReviewController :update review_path DELETE /reviews/:id HelloPhoenixWeb.ReviewController :delete admin_review_path GET /admin/reviews HelloPhoenixWeb.Admin.ReviewController :index admin_review_path GET /admin/reviews/:id/edit HelloPhoenixWeb.Admin.ReviewController :edit admin_review_path GET /admin/reviews/new HelloPhoenixWeb.Admin.ReviewController :new admin_review_path GET /admin/reviews/:id HelloPhoenixWeb.Admin.ReviewController :show admin_review_path POST /admin/reviews HelloPhoenixWeb.Admin.ReviewController :create admin_review_path PATCH /admin/reviews/:id HelloPhoenixWeb.Admin.ReviewController :update PUT /admin/reviews/:id HelloPhoenixWeb.Admin.ReviewController :update admin_review_path DELETE /admin/reviews/:id HelloPhoenixWeb.Admin.ReviewController :delete
どうやらscope
にas: :hogehoge
とするとhogehoge_
という接頭辞をpath helperに付与することができるみたいです。
他にも画像やユーザーをadmin
のスコープで管理したい場合は下記のようなルーティングになるとおもいます。
scope "/admin", as: :admin do pipe_through :browser resources "/images", HelloPhoenixWeb.Admin.ImageController resources "/reviews", HelloPhoenixWeb.Admin.ReviewController resources "/users", HelloPhoenixWeb.Admin.UserController end
HelloPhoenixWeb.Admin
が冗長です。
ここで"/"
のscope
同様、HelloPhoenixWeb.Admin
を渡してやると、省略できるようです。
scope "/admin", HelloPhoenixWeb.Admin, as: :admin do pipe_through :browser resources "/images", ImageController resources "/reviews", ReviewController resources "/users", UserController end
たいへんスッキリしました。
scopeのネスト
scope
のネストにも言及がありました。
一応実装としては可能なんですが、複雑化するのを避ける為に基本的には非推奨だそうです。
ただ、よくあるAPIのバージョンなんかが着いたURLを実装するのには使えそうです。
scope "/api", HelloPhoenixWeb.Api, as: :api do pipe_through :api scope "/v1", V1, as: :v1 do resources "/images", ImageController resources "/reviews", ReviewController resources "/users", UserController end end
こうネストすると/api/v1/hogehoge
というURLになります。WebのAPIでよく見る感じになりました。
一応こう実装しておけばバージョン毎に機能を残しておくこともできます。
同じパスのscope
同じルーティングパスを指定しないように注意すれば、scope
で同じパスを指定することもできるそうです。
defmodule HelloPhoenixWeb.Router do use Phoenix.Router ... scope "/", HelloPhoenixWeb do pipe_through :browser resources "/users", UserController end scope "/", AnotherAppWeb do pipe_through :browser resources "/posts", PostController end ... end
この例ではあまり旨みはでていませんが、コントローラーが増えてきてAdminの用にまとめたい時なんかは便利だと思います。
まだPipelinesとChannel Routesが残ってるんですが、Pipelinesの前にPlugをやっておいた方が良いと思うので一旦ここで切ります。