はじめに
前回は、Plugを見ていきました。
今回はControllerです。
次回はViewです。
概要
Controllerには、Action (RouterによってHTTPリクエストに対するレスポンスを行うための関数) を定義しています。
Actionは、Templateをレンダリングしたり、JSONレスポンスを返したりします。
PhoenixのControllerはPlugパッケージをベースに作成されていて、Controller自身もPlugです。
Phoenixで新規にアプリケーションを作成したとき、ルート用のControllerとしてPageController
が定義されます(web/controllers/page_controller.ex
)。
defmodule HelloPhoenix.PageController do
use HelloPhoenix.Web, :controller
def index(conn, _params) do
render conn, "index.html"
end
end
use HelloPhoenix.Web, :controller
は、HelloPhoenix.Web(web/web.ex
)のcontroller関数を呼び出します。
また、HelloPhoenix.Router(web/router.ex
)に、デフォルトルート用のController定義が生成されます。
scope "/", HelloPhoenix do
...
get "/", PageController, :index
Action
Actionは関数であり、Elixirの命名規則に従っていればどんな関数名でもかまいません。
デフォルトの:index
を変更したり、"?"を付けることもできます。
ただし、indexやrouteのresourcesで作成されるAction(show, new, createなど)は変更しない方が分かりやすいです。
Actionは、常にパラメータを二つとる関数で、第一引数は、Plugミドルウェアで生成された接続情報を表すパラメータです。第二引数は、リクエストから引き回されてきたパラメータのmapです。
データアクセス
Phoenix自身はデータアクセスレイヤーを持たずに、EctoによるDBアクセスを提供しています。
もちろん、OTPのETSやDETS、mnesia、他のデータアクセスライブラリを使用することもできます。
フラッシュメッセージ
ユーザアクションに応じて簡単なメッセージを返すようなケースがあります。たとえば、エラーによる更新失敗であったり、アプリケーションへのウェルカムメッセージなどです。こういうケースでは、フラッシュメッセージが有効です。
Phoenix.Controllerでは、put_flash/3
とget_flash/2
というフラッシュメッセージのKey-Valueペア用の関数が提供されています。
以下に、二つのフラッシュメッセージをセットする例を示します。
また、フラッシュメッセージを消去するclear_flash/1
という関数もあります。
defmodule HelloPhoenix.PageController do
. . .
def index(conn, _params) do
conn
|> put_flash(:info, "Welcome to Phoenix, from flash info!")
|> put_flash(:error, "Let's pretend we have an error.")
|> render("index.html")
end
end
フラッシュメッセージのキーに特に決まりはありませんが、:info
や:error
というのは一般的です。
フラッシュメッセージを表示するためには、Template側でフラッシュメッセージをget_flash/2
で取得して表示する必要があります。
なお、デフォルトのレイアウト(web/templates/layout/app.html.eex
)にはその仕組みが備わっています。
<p class="alert alert-info" role="alert"><%= get_flash(@conn, :info) %></p>
<p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
レンダリング
Controllerにおいて、コンテンツのレンダリングするには何種類か方法があります。もっとも簡単なのはPhoenixが提供するtext/2
関数です。
def show(conn, %{"id" => id}) do
text conn, "Showing id #{id}"
end
get "/path/:id"
をこのshow
関数にマッピングされているとします。ブラウザから/path/15
をリクエストした場合、想像通り、plain-textで"Showing id 15"が返されます。
同様にjson/2
関数を使えば、JSONを返すことができます。なお、Map型などをJSONに変換するにはPoisonライブラリ (Phoenixの依存ライブラリ)を使用しています。
show
を以下のように変更すると、/path/15
をリクエストに対して{ id: "15" }
を返すようになります。
def show(conn, %{"id" => id}) do
json conn, %{id: id}
end
Phoenixでは、テンプレートを使わずにHTMLを返すこともできます。text
、json
と同様にhtml/2
関数を使用します。使用方法は想像通りです。
ただ、テンプレートと違って<%= id %>
といった書き方はできません。
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
直接値を出力する関数以外にも、テンプレートを使って表示する方法も一般的です。テンプレートを使って表示する場合は、render/3
関数を使用します。
なお、内部的には、render/3
関数はPhoenix.Viewモジュールで定義されていますが、利便性の理由からPhoenix.Controllerにも定義しています。
テンプレートを使う例は、以下の通りです。
defmodule HelloPhoenix.HelloController do
use HelloPhoenix.Web, :controller
def show(conn, %{"messenger" => messenger}) do
render conn, "show.html", messenger: messenger
end
end
render/3
関数を使用するために、それぞれの名前の先頭を統一する必要があります。
例えば、HelloController
からはHelloView
を参照しますし、HelloView
はweb/templates/hello
ディレクトリを参照し、そのディレクトリ内にはrender/3
で指定したテンプレート(上記の例だと"show.html.eex")が無ければなりません。
render/3
には、第三引数としてパラメータを渡しますが、関数内でこれを制御することもできます。
def index(conn, _params) do
conn
|> assign(:message, "Welcome Back!")
|> render("index.html")
end
上記の例をplugを使って書くこともできます。
plug :assign_welcome_message, "Welcome Back"
def index(conn, _params) do
conn
|> render("index.html")
end
defp assign_welcome_message(conn, msg) do
assign(conn, :message, :msg)
end
なお、plugには指定されたアクションにだけ適用されるような書き方もあります。
plug :assign_welcome_message, "Hi!" when action in[:index]
レスポンス
200以外のレスポンスを返す場合には、send_resp/3
を使います。また、レスポンスのcontent-typeを指定する場合には、put_resp_content_type/2
を使用します。
def index(conn, _params) do
conn
|> put_resp_content_type("text/plain")
|> send_resp(201, "")
end
レイアウト
Layoutは、テンプレートのサブセットで、/web/templates/layout
に配置されます。Phoenixでは、デフォルトでapp.html.eex
というレイアウトを生成し、全てのテンプレートがこのレイアウト上に表示されます。
LayoutはあくまでTemplateに過ぎませんので、表示する際にはViewが必要になります。LayoutView
モジュールが/web/views/layout_view.ex
に定義されます。Layoutを/web/templates/layout
に配置する限りは、Layout用の新しいViewを生成する必要はありません。
Phoenix.Controller
モジュールのput_layout/2
関数を使って、使用するLayoutを変更することができます。この関数にfalse
を指定することでレイアウト無しで表示させることもできます。
def index(conn, params) do
conn
|> put_layout(false) # ← ()を忘れないように
|> render "index.html"
end
レンダリングフォーマット
同じ関数でHTMLやplain-textなどのフォーマットを切り替えたい場合は、以下のようにします。
defmodule HelloPhoenix.Router do
use HelloPhoenix.Web, :router
pipeline :browser do
plug :accepts, ["html", "text"]
...
end
def index(conn, _params) do
render conn, :index
end
...
http://localhost:4000/?_format=text
とすると、"index.text.eex"を参照し、htmlだと"index.html.eex"を参照するようになります。
Content-Typeのセット
xmlを出力したい場合は、以下のようにします。
def index(conn, _params) do
conn
|> put_resp_content_type("text/xml")
|> render "index.xml", content: some_xml_content
end
HTTPステータス
put_status/2
を使って、200以外のステータスに変更することができます。
def index(conn, _params) do
conn
|> put_status(202)
|> render("index.html")
end
リダイレクト
リダイレクトするには、redirect/2
関数を使います。
defmodule HelloPhoenix.Router do
use HelloPhoenix.Web, :router
. . .
scope "/", HelloPhoenix do
. . .
get "/", PageController, :index
end
# New route for redirects
scope "/", HelloPhoenix do
get "/redirect_test", PageController, :redirect_test, as: :redirect_test
end
end
defmodule HelloPhoenix.PageController do
. . .
def index(conn, _params) do
redirect conn, to: "/redirect_test"
end
def redirect_test(conn, _params) do
text conn, "Redirect!"
end
. . .
end
外部へのリダイレクトの場合、to:
ではなくexternal:
を使います