どうも、靖宗です。
今回は「Adding Pages」ということで、単純に訳してページを追加していく感じでしょう。
という事で基礎的なルーティング(どんなURLで何を表示するか)もおそらくこの章で触れることになるでしょう。
概要
この項目はざっくり何がどこにあるのか説明されているものなのでよくわかんなくても「あーそういうことね、完全に理解した(分かってない)」程度で十分だと思います。いっぱい使ってれば慣れるはず・・・
まず、Phoenixでプロジェクトを生成した時には下記のようなディレクトリ構造になっています。
├── _build ├── assets ├── config ├── deps ├── lib │ └── hello_phoenix │ └── hello_phoenix_web │ └── hello_phoenix.ex │ └── hello_phoenix_web.ex ├── priv ├── test
動的ファイル
今回関係してくるのはlib/hello_phoenix_web
で、この中は更に様々なファイルが生成されています。
├── channels │ └── user_socket.ex ├── controllers │ └── page_controller.ex ├── templates │ ├── layout │ │ └── app.html.eex │ └── page │ └── index.html.eex └── views │ ├── error_helpers.ex │ ├── error_view.ex │ ├── layout_view.ex │ └── page_view.ex ├── endpoint.ex ├── gettext.ex ├── router.ex
最初のハローワールドページを生成していたのは、このうちcontrollers
、templates
、views
の3つのようです。たぶんここら辺をいじったり真似て追加したりしていきます。
静的ファイル
静的ファイルはプロジェクトディレクトリ直下にあるassets
にあります。
ReactとかVue.jsとか入れるならここでしょう。
├── assets │ ├── css │ │ └── app.css │ ├── js │ │ └── app.js │ └── static │ └── node_modules │ └── vendor
Webの表示などにかかわらないファイル
ウェブの表示などにかかわらないファイル(たとえば、IoTサーバーで温度センサーから温度を取得してDBに記録する、など?)はlib/hello_phoenix/application.ex
などに記載していくようです。
A New Route
さてここからが本題です。
あるURLにアクセスしたときに何が表示されるかはルーティングによって決まります。
このルーティングは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
✌('ω')。o(????????????)
まるで今までのElixir入門編をあざ笑うかのごとく、通常の記法ではなくゴリゴリのマクロです。
該当箇所は
scope "/", HelloPhoenixWeb do pipe_through :browser get "/", PageController, :index end
だと思われますが、scope
なんてしりません。内部実装を知りたい方はPhoenixのリポジトリにscopeが定義されてますのでそちらを参照するのもアリだと思います。
これを思うと、OSSコミッター的ななにかを目指さない限りは「こういうものだ」と割り切って使う方がいい気がしてきました。
めげずに進みますと、おそらくGETメソッドのレスポンスはPageController
の:index
を返すぜ!って流れだと思います。PageController
はlib/hello_phoenix_web/controllers/page_controller.ex
に記載されてます。
詳しいルーティングは次回に回すとして、ここではhttp://localhost:4000/hello
というページを追加するためにlib/hello_phoenix_web/router.ex
を修正します。
scope "/", HelloPhoenixWeb do pipe_through :browser get "/", PageController, :index get "/hello", HelloController, :index end
scope
のブロックを増やすのかと思いきやgetを追記していく形でよさそうです。
同じ:index
を返しているようですが、コントローラーが異なっています(HelloController
)。
A New Controller
コントローラーは返すページなどのモジュールです。レンダリングと、たぶんデータベースのやりとりとかもココに書くのでしょう。
PageController
があるディレクトリにHelloController
を作って行きます。というかコピペして名前を変えてしまいましょう。
lib/hello_phoenix_web/controllers/hello_controller.ex
を作成します。
defmodule HelloPhoenixWeb.HelloController do use HelloPhoenixWeb, :controller def index(conn, _params) do render(conn, "index.html") end end
詳しいことは別のタイミングでやります。兎に角いまは付け焼き刃でガンガン実装していきます。
ここのindex/2
は、一個目の引数がおそらく接続に必要な情報(ヘッダとか?)で、_params
がPOSTパラメータなどでしょう。今は特にパラメータが渡されることは無い筈なので、変数名に_
が着いてます。
ここで、重要な箇所のrender/2
という関数ですが、この関数はテンプレートを呼び出してHTMLデータを作成する関数のようです。
PhoenixはこのHelloController
という名前のController
以前の箇所名を参照してテンプレートを取得するようです。
なのでlib/hello_phoenix_web/templates/hello
フォルダを作成し、index.html.eex
を作成します。lib/hello_phoenix_web/templates/page
の物と同様でいいとおもいます。
A New View
コントローラーからデータを受け取ってレンダリング前に加工するのにビューが用いられるそうです。
例えば、苗字と名前のデータがそれぞれ別にあって、フルネームがレンダリングされる場合はフルネーム=苗字+名前の処理はビューでやるそうです。
(この辺は宗教な気はしますが)
ただし、どのみちビューのファイルは必要なようです。lib/hello_phoenix_web/views/hello_view.ex
を作成します。
defmodule HelloPhoenixWeb.HelloView do use HelloPhoenixWeb, :view end
Phoenixは結構名前にうるさいようです。基本モジュール名はキャメルケース(CamelCase)で、ファイル名はスネークケース(snake_case)にするようです。そしてコントローラー名をHogeController
にした時、ビューはHogeView
にするという、ある程度命名規則が決まっているようです。
不自由に見えて実はこの辺はフレームワークの仕様として決めてしまえば明示的に誰が見ても機能が保証される気がするのでいい気がします。
A New Template
lib/hello_phoenix_web/templates/hello/index.html.eex
がHelloController
のrender
でレンダリングされます。
まったく同じ物というのでは全体像がつかみにくいのでやはり編集します。
<div class="phx-hero"> <h2>Hello World, from Phoenix!</h2> </div>
シンプル!
完全なHTMLではなく、一部が挿入されるようです。
とりあえずコレで大体が完了したようなのでhttp://localhost:4000/hello
にアクセスしてチェックします。
mix phx.server
を終了していなければ、追加変更は検知してコンパイルしてくれるそうです。(地味に便利!)
特に何もせずにアクセスしてみましょう。
どうやらヘッダ部分(Phoenix Frameworkと書いてある箇所)からして共通のテンプレートがありそうです。
lib/hello_phoenix_web/templates/layout/app.html.eex
がこのテンプレートに該当するようです。
このapp.html.eex
の
<%= render(@view_module, @view_template, assigns) %>
という箇所に先ほど修正したテンプレートが入るという流れのようです。
これで一応荒削りながら新しいページを追加することができました。
Another New Page
とはいえもう少しWebアプリケーションっぽくしたいところです。
そこで、URLから動的にページをレンダリングさせてみます。
A New Route
基本的には先ほどの流れとほぼ同様。ルーティングファイルを修正していきます。
lib/hello_phoenix_web/router.ex
に追記します。
... get "/hello", HelloController, :index get "/hello/:messenger", HelloController, :show # この行を追記 ...
URLにアトムを使用すると、その箇所がマップとしてコントローラーに渡されるようです。
この場合、http://localhost:4000/hello/Frank
とアクセスすると、引数のマップに"messenger"というキーで"Frank"が取り出せるマップが渡されます。
A New Action
追記した箇所は先ほどと同じHelloController
を使用しているのでHelloController
にshow
関数を追記します。
lib/hello_phoenix_web/controllers/hello_controller.ex
を修正します。
... def show(conn, %{"messenger" => messenger}) do render(conn, "show.html", messenger: messenger) end ...
index
の_params
だった箇所にマップの型が入ってます。これは関数が呼び出されたときにパターンマッチでmessenger
という変数を抽出しています。
もし、paramsの他の値が必要になる場合は
def show(conn, %{"messenger" => messenger} = params) do ... end
としておけば良いそうです。
極力セーフな感じに設計するならパターンマッチで抽出してる方がきちんとエラー処理できそうです。
A New Template
ここまでくれば後はテンプレートを追加するだけです。
(ビューは特に何もしてないのでlib/hello_phoenix_web/views/hello_view.ex
が存在していればOK)
lib/hello_phoenix_web/templates/hello/show.html.eex
を作成していきます。
<div class="phx-hero"> <h2>Hello World, from <%= @messenger %>!</h2> </div>
<%= @messenger %>
の箇所はEEx独自のタグだそうで、レンダリングする時に変数などを代入できます。
特に間違ってなければ、この時点で既にhttp://localhost:4000/hello/hogehoge
でアクセスできる筈です。
命名規則が若干ヤヤコシイですが、慣れればどうということは無さそうです。