技術メモ

プログラミングとか電子工作とか

Phoenix入門 (第4章 Routing その1)

f:id:ysmn_deus:20190219130922p:plain

どうも、靖宗です。
前回ざっくりとしかやらなかったルーティング。ちょっとページが長いので数回に分けて記事にしたいと思います。

router.exを見てみる

前回も触れていますが、ルーティングはlib/hello_phoenix_web/router.exに記載されています。

defmodule HelloPhoenixWeb.Router do
  use HelloPhoenixWeb, :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 "/", HelloPhoenixWeb do
    pipe_through :browser

    get "/", PageController, :index
  end

  # Other scopes may use custom stacks.
  # scope "/api", HelloPhoenixWeb do
  #   pipe_through :api
  # end
end

use HelloPhoenixWeb, :routerの箇所でルーティングするためのマクロを使えるようにします。HelloPhoenixWeb__using__

  defmacro __using__(which) when is_atom(which) do
    apply(__MODULE__, which, [])
  end

というマクロなので、HelloPhoenixWebrouter/0を実行しているようなものと思えば良さそうです。肝心のrouter関数は

  def router do
    quote do
      use Phoenix.Router
      import Plug.Conn
      import Phoenix.Controller
    end
  end

となっており、PhoenixのRouterが参照され、scopeなどのマクロが使える様になるといった流れです。
本来であればpipelinescopeなどのマクロの実装もきちんと追った方が理解が深まるかと思いますが、内部の実装を詳しく知らなくても開発が進むようになっているのがマクロの筈です。今はスルーしてもっと内部実装を知りたいときに見ることにします。
現状の予想ではpipeline :browserpipeline :apiという書かれ方をしているので、おそらくブラウザからのアクセスとフロントエンドからのAPIとしてのアクセスとで細かい挙動を変えれるのだと思います。
pipelinepipe_throughは後々"Pipelines"という項目が出てくるようなのであとで詳細を確認します。

scope中の

get "/", PageController, :index

に関しては前回拡張したとおり、GETメソッドでリクエストが来たときにコントローラーの関数(前回はレンダリング関数)を返すような動きをしていました。
第1引数("/")はパス(サーバーが置かれている場所の相対パス)で、第2,3引数はコントローラーとそのアクション(関数)です。モジュール名と関数が分離して与えられているのはMix and OTP編の分散処理あたりが内部実装としてあるのでしょう。

getマクロはmatch/5という関数の1節に該当しているようで、展開されると

def match(:get, "/", PageController, :index, [])

という実装になるそうです。

このgetマクロを列挙する形でページを増やしましたが、このget句は他のウェブフレームワーク同様上から順に実行され、マッチすれば別のget句は精査されないままレスポンスが完了するようです。
前回は"/"から並べていきましたが、全部一番上のindexにならなかったのは完全一致だからなんでしょうか?

Examining Routes

Phoenixにはルーティングを確認する機能もついてるようです。プロジェクトのルートディレクトリ(一番上のディレクトリ)でmix phx.routesというコマンドをうってやれば見れるみたいです。

PS > mix phx.routes
Compiling 2 files (.ex)
 page_path  GET  /                  HelloPhoenixWeb.PageController :index
hello_path  GET  /hello             HelloPhoenixWeb.HelloController :index
hello_path  GET  /hello/:messenger  HelloPhoenixWeb.HelloController :show

page_pathhello_pathは謎ですがあとで言及されるそうです。
GETメソッドでリクエストがどういうURLで来るとどのコントローラーのどのアクションが呼び出されるかが一目瞭然です。これは良さそう。

Resources

getというマクロを使いましたが、これはGETメソッドと重なって直感的に挙動が分かりやすいです。
おそらくputpostもあることでしょう。

ここで謎のマクロresourcesが出てきます。とりあえず謎なので挙動を見てみます。

  scope "/", HelloPhoenixWeb do
    pipe_through :browser

    get "/", PageController, :index
    resources "/users", UserController
  end

まだUserControllerというコントローラーは実装していませんが、さっきおぼえたてのmix phx.routesを使ってみます。

PS > mix phx.routes
Compiling 2 files (.ex)
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

なるほど!基本的にCRUD操作などをするデータの対象は、大体ルーティングが決まってると思います。
そのひな形をresourcesというマクロを使うと一発で色々ルーティングできちゃうようです。
ちなみに:only:exceptで実装を限定化できるようです。

resources "/posts", PostController, only: [:index, :show]

これはこうなります。

post_path  GET  /posts      HelloPhoenixWeb.PostController :index
post_path  GET  /posts/:id  HelloPhoenixWeb.PostController :show

逆に:exceptしてみるとこうなります。(まぁ、:index:showをexceptするなんてありえないですが)

resources "/posts", PostController, except: [:index, :show]
post_path  GET     /posts/:id/edit  HelloPhoenixWeb.PostController :edit
post_path  GET     /posts/new       HelloPhoenixWeb.PostController :new
post_path  POST    /posts           HelloPhoenixWeb.PostController :create
post_path  PATCH   /posts/:id       HelloPhoenixWeb.PostController :update
           PUT     /posts/:id       HelloPhoenixWeb.PostController :update
post_path  DELETE  /posts/:id       HelloPhoenixWeb.PostController :delete

Forward

たぶんウェブリクエストを別のWebアプリケーションに投げる機能?

defmodule HelloWeb.Router do
  use HelloWeb, :router

  ...

  scope "/", HelloWeb do
    ...
  end

  forward "/jobs", BackgroundJob.Plug
end

こんなかんじで書くらしい。
正直Plugがなんなのかよく分かってないのでイマイチ文章が分かりませんでした。
とりあえず詰まったらまた戻るかんじで。

Path Helpers以降は次回!