/var/log/messages

Apr 18, 2019 - 4 minute read - Comments - programming

phoenix excersize (11)

継続は Views の More About Views の項。

More About Views

  • View と Template の連携について
  • Phoenix.View はその __using__/1 マクロの use Phoenix.Template という行を通じて Template のふるまいにアクセスする
  • Phoenix.Template はテンプレートを扱うための便利メソドをたくさん提供

Phoenix が提供する web/views/page_view.ex で少し色々試してみる。message/0 の追加。

    defmodule HelloWeb.PageView do
      use HelloWeb, :view
    
      def message do
        "Hello from the view!"
      end
    end

で、新しいテンプレ作成、とのこと。web/templates/page/test.html.eex

This is the message: <%= message() %>

iex セッションにて実行、とのこと。プロジェクトの root にて iex -S mix を実行することでテンプレの明示的なレンダリングができる、とのこと。

iex(1)> Phoenix.View.render(HelloWeb.PageView, "test.html", %{})
  {:safe, [["" | "This is the message: "] | "Hello from the view!"]}

これは面白い。iex から Phoenix.View.render/3 が呼び出せるのか。

  • 戻りは :safe な atom で始まるタプルと補完されたテンプレートの結果として取得できる io リスト
  • :safe は Phoenix がレンダリングされたテンプレートの内容をエスケイプしたことを意味する
  • Phoenix.HTML.Safe プロトコルをいくつかの実装で定義
    • atom
    • bitstring
    • list
    • integer
    • float
    • tuple

テンプレート少し変更。

I came from assigns: <%= @message %>
This is the message: <%= message() %>

r でモジュールのリコンパイルらしい。

iex(2)> r HelloWeb.PageView
warning: redefining module HelloWeb.PageView (current version loaded from _build/dev/lib/hello/ebin/Elixir.HelloWeb.PageView.beam)
  lib/hello_web/views/page_view.ex:1

{:reloaded, HelloWeb.PageView, [HelloWeb.PageView]}

iex(3)> Phoenix.View.render(HelloWeb.PageView, "test.html", message: "Assigns has an @.")
{:safe,
  [[[["" | "I came from assigns: "] | "Assigns has an @."] |
  "\nThis is the message: "] | "Hello from the view!"]}

@ に注意、とのこと。また、HTML エスケイプの例が以下とのこと。

iex(4)> Phoenix.View.render(HelloWeb.PageView, "test.html", message: "<script>badThings();</script>")
{:safe,
  [[[["" | "I came from assigns: "] |
     "&lt;script&gt;badThings();&lt;/script&gt;"] |
    "\nThis is the message: "] | "Hello from the view!"]}

戻りがエスケイプされていることが分かります。レンダリングされた文字列だけが必要な場合には render_to_iodata/3 が使える、とのこと。

iex(5)> Phoenix.View.render_to_iodata(HelloWeb.PageView, "test.html", message: "Assigns has an @.")
[[[["" | "I came from assigns: "] | "Assigns has an @."] |
  "\nThis is the message: "] | "Hello from the view!"]

A Word About Layouts

  • レイアウトは単なるテンプレート
  • 他のテンプレートと同様に View がある
  • 新規に作成したプロジェクトでは web/views/layout_view.ex
  • レンダリングされた View から得られた文字列がどのようにレイアウト内に収まる?

web/templates/layout/app.html.eex の body の真ん中らへんに以下があるはず。

<%= render(@view_module, @view_template, assigns) %>

これが Controller からの View Module とその Template が文字列にレンダリングされ、レイアウトに配置される場所。

The Error View

  • Phoenix は web/views/error_view.ex にある ErrorView と呼ばれる View を持つ
  • ErrorView の目的は 404 および 500 のエラーを処理すること

実装は以下。

defmodule HelloWeb.ErrorView do
  use HelloWeb, :view

  def render("404.html", _assigns) do
    "Page not found"
  end

  def render("500.html", _assigns) do
    "Server internal error"
  end
end

開発環境ではデフォルトでデバッグページが表示されるので config/dev.exs で以下の設定を盛り込む必要がある。

use Mix.Config

config :hello, HelloWeb.Endpoint,
  http: [port: 4000],
  debug_errors: false,
  code_reloader: true,

設定ファイルを修正したらサーバの再起動が必要とのこと。再起動したら http://localhost:4000/such/a/wrong/ みたいな URL にアクセスして表示を確認しましょう。

表示されるページに色々手を加えてみる模様。とりあえず表示されるコンテンツの根拠は ErrorView である、ということは分かっています。render/2 はどこから呼び出されているのか。

  • Phoenix.Endpoint.RenderErrors モジュールで定義されている render/5
  • このモジュールの全体的な目的はエラーを catch して View を使ってそれらをレンダリングすること
  • ここまで理解できればエラーページを改善できる

Phoenix は ErrorView を生成する。ただし web/templates/error は無いので作成して 404.html.eex を追加してそれにマークアップを付けてみる。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="description" content="">
    <meta name="author" content="">

    <title>Welcome to Phoenix!</title>
    <link rel="stylesheet" href="/css/app.css">
  </head>

  <body>
    <div class="container">
      <div class="header">
        <ul class="nav nav-pills pull-right">
          <li><a href="https://hexdocs.pm/phoenix/overview.html">Get Started</a></li>
        </ul>
        <span class="logo"></span>
      </div>

      <div class="phx-hero">
        <p>Sorry, the page you are looking for does not exist.</p>
      </div>

      <div class="footer">
        <p><a href="http://phoenixframework.org">phoenixframework.org</a></p>
      </div>

    </div> <!-- /container -->
    <script src="/js/app.js"></script>
  </body>
</html>

これで render/2 が使える、とのこと。Phoenix は 404.html.eexrender("404.html", assign) としてプリコンパイルすることがわかっているので以下を ErrorView から削除できる。

- def render("404.html", _assigns) do
-   "Page not found"
- end

再度、http://localhost:4000/such/a/wrong/ にアクセスすると上の上等なエラーページが表示されるはず。

Rendering JSON

  • Phoenix View は JSON レンダリングに最適
  • Phoenix は Jason を使って Map を JSON にエンコード
  • View で行なう必要があるのは Map に応答したいデータをフォーマットすることだけ

実装確認など。

defmodule HelloWeb.PageController do
  use HelloWeb, :controller

  def show(conn, _params) do
    page = %{title: "foo"}

    render(conn, "show.json", page: page)
  end

  def index(conn, _params) do
    pages = [%{title: "foo"}, %{title: "bar"}]

    render(conn, "index.json", pages: pages)
  end
end

なるほど。責任分解、という観点でも上の書き方は良さげに見えます。

  • show.json を render/3 に渡している
  • パタンマッチを使って View の定義が可能
defmodule HelloWeb.PageView do
  use HelloWeb, :view

  def render("index.json", %{pages: pages}) do
    %{data: render_many(pages, HelloWeb.PageView, "page.json")}
  end

  def render("show.json", %{page: page}) do
    %{data: render_one(page, HelloWeb.PageView, "page.json")}
  end

  def render("page.json", %{page: page}) do
    %{title: page.title}
  end
end
  • render(conn, "index.json", pages: pages)render("index.json", %{pages: pages}) を呼び出す
  • これ、render_many だと配列で render_one だと一要素の、なのかな

あと、割り当てに使われる名前は View から決められる、とのこと。

  • PageView は %{page: page} を使うし AutherView は %{author: autor}
  • as というオプションで上書き可能
def render("page_with_authors.json", %{page: page}) do
  %{title: page.title,
    authors: render_many(page.authors, AuthorView, "author.json", as: :writer)}
end

代わりに %{writer: writer} が使われている例が上の記載です。

phoenix excersize (10) phoenix excersize (12)

comments powered by Disqus