技術メモ

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

Phoenix入門 (第9章 Templates)

f:id:ysmn_deus:20190219130922p:plain

どうも、靖宗です。 今回はTemplatesということでテンプレートに関して深掘りしていきます。
大体Viewsで知りたいことは知った感もあるのですが、念のためにチェックしておきます。

Templates

Viewsでも取り扱いましたがテンプレートはHTMLやJSONXMLなどで利用できます。
基本的にこれらのテンプレートはコンパイル時に読み込まれ、実行中はメモリ上に展開されているようです。なので早いと。

EExというテンプレートシステム(Elixirのライブラリ?)が採用されているようで、RubyでいうところのERBだそうです。自分はRuby触ったことないのでなにがなんだかさっぱりです。
あんまり気にしなくてもビューは作れそうです。

Examples

hello_phoenix_web.ex

とりあえずサンプルを見ていきましょう。 Adding Pagesは経験してる前提で細かい説明はすっ飛ばします。 まずはルーティングを確認しておきます。lib/hello_phoenix_web/router.exです。

defmodule HelloPhoenixWeb.Router do
  use HelloPhoenixWeb, :router
...
  scope "/", HelloPhoenixWeb do
    pipe_through :browser

    get "/", PageController, :index
    get "/test", PageController, :test
  end
...

/testのルーティングが増えてます。なのでコントローラにアクションを追加します。

...
  def test(conn, _params) do
    render(conn, "test.html")
  end
...

ここで、なにやらaction_name/1controller_module/1が要る、と書かれておりlib/hello_phoenix_web.exを編集します。

...
  def view do
    quote do
      use Phoenix.View,
        root: "lib/hello_phoenix_web/templates",
        namespace: HelloPhoenixWeb

      # Import convenience functions from controllers
      import Phoenix.Controller, only: [get_flash: 1, get_flash: 2, view_module: 1, action_name: 1, controller_module: 1]
...

たぶんこの章のサンプルの動作に必要なものをインポートしているのでしょう。
細かい事は後回しにして次に進みます。

次はHelloPhoenixWeb.PageViewを編集します。
先ほどインポートしたaction_name/1controller_module/1を使ってhandler_info/1という関数を作っています。

defmodule HelloPhoenixWeb.PageView do
  use HelloPhoenixWeb, :view

  def handler_info(conn) do
    "Request Handled By: #{controller_module(conn)}.#{action_name(conn)}"
  end

  def connection_keys(conn) do
    conn
    |> Map.from_struct()
    |> Map.keys()
  end
end

あと、Plug.Connの中にあるキーでも吐き出すようなconnection_keys/1という関数も追加しました。
たぶん表示される物を見ればなんなのか予想できそうなので今は放置します。

次にテンプレートを追加します。lib/hello_phoenix_web/templates/page/test.html.eexを作成します。

<div class="phx-hero">
  <p><%= handler_info(@conn) %></p>
</div>

レイアウトのおかげで書くのはこれだけです。どうやらlocalhost:4000/testにアクセスしたときにコントローラのモジュール名やアクション名を表示するようなページを書いていたようです。
早速http://localhost:4000/testにアクセスします。

f:id:ysmn_deus:20190311100804p:plain

バッチリです✌('ω')
当たり前っちゃ当たり前なんですが、ここで利用しているhandler_info(@conn)はこのHelloPhoenixWeb.PageViewレンダリングされるlib/hello_phoenix_web/templates/pageでしか使えません。
あくまでテンプレートはビュー上で評価されていますので、ビューの関数のみ利用可能です。

Displaying Lists

テンプレートにはリストを表示するディレクティブのようなものもあるようです。
lib/hello_phoenix_web/templates/page/test.html.eexを下記のように編集します。

<div class="phx-hero">
  <p><%= handler_info(@conn) %></p>

  <h3>Keys for the conn Struct</h3>

  <%= for key <- connection_keys(@conn) do %>
    <p><%= key %></p>
  <% end %>
</div>

先ほどと同様にhttp://localhost:4000/testにアクセスします。

f:id:ysmn_deus:20190311112220p:plain

conのキーが表示されました。

Render templates within templates

テンプレートの構造が複雑になってきて可読性が下がる場合などがあります。
そもそもレイアウト(app.html.eex)も可読性を上げたりヘッダやフッタなどの冗長性を下げたりする仕組みですが、これはどのテンプレートでも取り入れられます。
つまりテンプレート内でテンプレートを呼び出せます!

早速やってみましょう。lib/hello_phoenix_web/templates/page/key.html.eexを作成します。

<p><%= @key %></p>

先ほどはテンプレート内で発生した変数keyを利用していましたが、今回はテンプレートをレンダリングする際に渡される変数keyなので@が着いてます。

今回はショボいですが、これがAmazonの検索結果の1個1個のアイテムだとしたらテンプレートが分かりやすくなり、恩恵を得られそうです。
次にlib/hello_phoenix_web/templates/page/test.html.eexを編集します。

<div class="phx-hero">
  <p><%= handler_info(@conn) %></p>

  <h3>Keys for the conn Struct</h3>

  <%= for key <- connection_keys(@conn) do %>
    <%= render("key.html", key: key) %>
  <% end %>
</div>

keyの変数名を利用していたところにrender/2関数を呼んでいます。
同様にhttp://localhost:4000/testにアクセスすれば、先ほどと変わらないページが表示されると思います。

以外とこの辺はJSONの構造などにも利用できそうです。

Shared Templates Across Views

上記で作成したような細かいテンプレートを別のビューで呼び出したい時があると思います。
そういう際には

<div class="phx-hero">
  ...

  <%= for key <- connection_keys(@conn) do %>
    <%= render(HelloPhoenixWeb.PageView, "key.html", key: key) %>
  <% end %>
</div>

としてモジュール名をきちんと表記してrender/3を利用することが想定されます。が、いかんせんメンテナンス性に欠けそうです。
そこでPhoenixがベストプラクティスとして提案しているのがSharedViewというものを作成してそこからロードする、といった手法です。
他のビューと同様にlib/hello_phoenix_web/views/shared_view.exを作成し、lib/hello_phoenix_web/templates/sharedディレクトリも作成します。
shared_view.ex

defmodule HelloPhoenixWeb.SharedView do
  use HelloPhoenixWeb, :view
end

だけでいいです。(必要に応じてカスタマイズされるかもしれませんが、大体のケースはこれでいいはず)

たとえば今回であればkey.html.eexlib/hello_phoenix_web/templates/sharedに入れて利用する場合であればlib/hello_phoenix_web/templates/page/test.html.eexの該当箇所は

<%= for key <- connection_keys(@conn) do %>
  <%= render(HelloWeb.SharedView, "key.html", key: key) %>
<% end %>

となります。
規模が大きくなったときには有用な方針だと思います。
あくまで方針なので、どうするかは開発者の自由ではあるようです。