どうも、靖宗です。 Controllersの続きです。また記事3個になりそうです。
- Rendering
- Sending responses directly
- Assigning Layouts
- Overriding Rendering Formats
- Setting the Content Type
- Setting the HTTP Status
Rendering
コントローラはコンテンツ(ウェブサイトなどURLで表示されるやつ)のレンダリング方法が何種類かあるそうです。
テキスト
一番シンプルなのがtext/2
関数。
def show(conn, %{"id" => id}) do text(conn, "Showing id #{id}") end
ルーティングの箇所でget "/hoge/:id", HogeController :show
などでルーティングされている想定です。
params
引数はパターンマッチしています。おそらくブラウザにはシンプルに"Showing id 15"などとidに応じたテキストデータがレンダリングされることでしょう。
あんまり使わないと思います。
JSON
これはたまにつかうかな?シンプルなJSONを返す時はjson/2
関数だそうです。
def show(conn, %{"id" => id}) do json(conn, %{id: id}) end
先ほどと同様に指定のパスのidにアクセスするとその数字が帰ってくると思います。
(例えば、http://localhost:4000/hoge/15
など)
{"id": "15"}
HTML(テンプレートファイル無し)
一応HTMLをハードコードすることもできるそうです。
たぶんテンプレートファイルを使った方がいいです。
def show(conn, %{"id" => id}) do html(conn, """ <html> <head> <title>Passing an Id</title> </head> <body> <p>You sent in id #{id}</p> </body> </html> """) end
HTMLのテンプレートファイルと違う箇所は#{id}
なのか<%= id %>
なのかといったところでしょうか。#{hogehoge}
というのはElixirの文字列の中に変数を展開する書き方だったと思います。
render/3
関数
今までの記事でも大体render/3
関数を使ってたと思います。APIとかでなければこちらを使うのが良いでしょう。
render/3
はPhoenix.Controller
の関数ではなくPhoenix.View
で定義されている関数です。(Phoenix.Controller
ではエイリアスされているようです。)
一応なんども出てきてますが、render/3
のサンプルも掲載します。
defmodule HelloPhoenixWeb.PageController do use HelloPhoenixWeb, :controller def show(conn, %{"messenger" => messenger}) do render(conn, "show.html", messenger: messenger) end end
render/3
が正常に動作するには命名規則に従ったviewファイルが必要です。(Adding Pagesなどを参照。)
つまり、HelloPhoenixController
というコントローラでrender/3
を使うにはHelloPhoenixView
が必要で、HelloPhoenixView
を使うにはlib/hello_phoenix_web/templates/hello
というディレクトリとshow.html.eex
というテンプレート(ただしここはrender
関数による)が必要です。
上の例ではテンプレートに渡す変数としてmessenger
を第三引数として渡していますが、Plug.Conn
の中に含めて渡すこともできるようです。
def index(conn, _params) do conn |> assign(:message, "Welcome Back!") |> render("index.html") end
本来であればPlug.Conn.assign/3
ですが、Phoenix.Controller
ではPlug.Conn
がインポートされているようなのでassign/3
として使えます。
assign/3
はパイプラインで渡して複数の変数を登録しておけます。
def index(conn, _params) do conn |> assign(:message, "Welcome Back!") |> assign(:name, "Dweezil") |> render("index.html") end
こうするとテンプレート内でmessage
もname
も両方使えます。
各アクションで利用する変数にデフォルト値をアサインしておきたい場合なんかもあると思います。
そういう場合はコントローラ内で変数をアサインする関数を作っておき、plug
マクロで登録しておきます。
plug :assign_welcome_message, "Welcome Back" def index(conn, _params) do conn |> assign(:message, "Welcome Forward") |> render("index.html") end defp assign_welcome_message(conn, msg) do assign(conn, :message, msg) end
最初にplug :assign_welcome_message, "Welcome Back"
と書いておけば全てのアクションに対して適応されるようです。
全てのアクションではなく、特定のアクションにのみ適応させることもできます。plug関数の記述の後にguard句の様な物を付けます。
defmodule HelloPhoenixWeb.PageController do use HelloPhoenixWeb, :controller plug :assign_welcome_message, "Hi!" when action in [:index, :show] ...
直感的で分かりやすいとは思います。
Sending responses directly
特にレンダリングするでもなく処理に成功したレスポンスを返したい場合はsend_resp/3
が使える様です。
def index(conn, _params) do conn |> send_resp(201, "") end
コンテンツのタイプをヘッダに加えておきたい場合はconn
のパイプラインにput_resp_content_type/2
を書いておきます。
def index(conn, _params) do conn |> put_resp_content_type("text/plain") |> send_resp(201, "") end
201レスポンスなどは余り経験が無いためイマイチ使いどころが分かりませんが、やるときはこうするんだな~程度に心にとどめておきます。
Assigning Layouts
テンプレートのレイアウトのお話?多分テンプレート上で呼べるテンプレート的な奴だと思います。
lib/hello_phoenix_web/templates/layout
に入ってるやつです。
lib/hello_phoenix_web/view
ディレクトリにLayoutView
モジュールがあることを鑑みると、普通のビューである事が分かります。おそらく内部実装でrender
する際にこのビュー+render
で指定してるビューが結合してレンダリングするのでしょう。
Phoenix.Controller
モジュールではput_layout/2
関数が利用可能で第一引数がconn
、第二引数がレンダリングするレイアウトのテンプレート名となっているそうです。雰囲気としてはrender/2
関数に似ているのでしょう。
ただし、第二引数にfalse
を渡すとレイアウトをレンダリングしない様にすることも可能だそうです。
def index(conn, _params) do conn |> put_layout(false) |> render("index.html") end
こうするとlib/hello_phoenix_web/templates/layout/app.html.eex
に書かれている箇所が消えて変わり果てた姿を目の当たりにするでしょう。
レイアウトを別の物に変更したい場合にもput_layout/2
は使えるようです。
例えばapp.html.eex
と同じディレクトリにadmin.html.eex
を用意して(コピペして編集するのがいいでしょう)そっちを読み込ませたい場合は
def index(conn, _params) do conn |> put_layout("admin.html") |> render("index.html") end
とすれば良いそうです。
Overriding Rendering Formats
あらかじめレンダリングする形式が分かっている場合はクエリ文字列を渡してやってレンダリングする形式をURLから指定することもできるそうです。
下記サンプルは新しいプロジェクトを生成した初期状態、つまりPageController
が生成されていてPageView
もあってlib/hello_phoenix_web/templates/page
にindex.html.eex
がある時を想定してます。
デフォルトではコントローラにはindex
のアクションが書かれています。
def index(conn, _params) do render(conn, "index.html") end
ここで、lib/hello_phoenix_web/templates/page
にindex.text.ee
というファイルを追加して、内容を下記の通りにします。
OMG, this is actually some text.
ただのテキストです。
次に、ルーティング設定(lib/hello_phoenix_web/router.ex
)にテキストフォーマットもリクエストを受ける旨を記載します。
defmodule HelloPhoenixWeb.Router do use HelloPhoenixWeb, :router pipeline :browser do plug :accepts, ["html", "text"] # "text"を追記 plug :fetch_session plug :fetch_flash plug :protect_from_forgery plug :put_secure_browser_headers end ...
お次にコントローラのrender/2
を書き換えます。
def index(conn, _params) do render(conn, :index) # "index.html"を:indexに end
これだけで良いそうです。http://localhost:4000/?_format=text
というクエリ文字列を付与した状態でアクセスすると、確かにテキストの方がレンダリングされます。(なにも付与しない場合は通常のページが表示されます。)
また、フォーマットだけでなく、クエリ文字列にパラメータを与えてテンプレートに適応することもできるそうです。index
アクションを変更します。
def index(conn, params) do render(conn, "index.text", message: params["message"]) end
テキストのテンプレートは下記のように変更しておきます。
OMG, this is actually some text. <%= @message %>
こうすることでhttp://localhost:4000/?_format=text&message=hogehoge
とした際にindex
アクションに渡されるparams
に"message"
というキーで"hogehoge"
が渡されるようです。
さらっと出てきてますが、クエリ処理は勝手にやってくれるみたいですね。
Setting the Content Type
Content Typeを指定してテンプレートをレンダリングしてレスポンスを送る方法。
Sending responses directlyの項目でもあったput_resp_content_type/2
を使います。
def index(conn, _params) do conn |> put_resp_content_type("text/xml") |> render("index.xml", content: some_xml_content) end
別段つまる所はないかと。
たぶんJSONのテンプレート決めてレンダリングするときとかにも使えそう。
Setting the HTTP Status
レスポンスにステータス番号を付与する方法。
put_resp_content_type/2
と同様にconnのパイプラインにput_status/2
関数を挟む感じだそうです。
def index(conn, _params) do conn |> put_status(202) |> render("index.html") end
ステータスコードはアトムでも管理されているようで、該当ステータスはPlugのドキュメントを参照とのこと。
例えばnot foundのページを明示的にレンダリングするなら下記の通り。
def index(conn, _params) do conn |> put_status(:not_found) |> render("index.html") end
できるだけステータスコードはアトムで書くのがミスが減りそう。
ちゃんと書くには下記の通りにするそうです。
def index(conn, _params) do conn |> put_status(:not_found) |> put_view(HelloPhoenixWeb.ErrorView) |> render("404.html") end
ErrorView
なんかはまだ聞いたことがないので別の章(おそらくViews)でやるのでしょう。
あまりステータスコードをハードコーディングしない方が賢明な気はします。
Redirection以降はまた次回!