For tokyo.ex #2 LT
- 2. Phoenix Tips and Tricks
• 5/2(月)にChris McCordが、「Phoenixに関してこういうところに
注意すると良いよ」という主旨のブログを出した
• 内容は以下の3つ
1. コントローラ内でaction/2をオーバーライドする
2. ErrorViewはコントローラで直接レンダリングする
3. Task.awaitする必要がなければ、Task.asyncを使わない
- 3. 本題の前に、Phoenixの処理
の流れをおさらい
かなりざっくりと…
connection
|> endpoint
|> router
|> pipelines
|> controller
詳細な足回りについては、古城さんの前回の発表資料を参考
https://speakerdeck.com/hidetakakojo/tokyo-dot-ex-number-1-
phoenixframeworkfalsezu-hui-ri
既存のPlugモジュールについては、菅原さんの前々回の発表資料を参考
http://sssslide.com/speakerdeck.com/winebarrel/elixir-meetup-number-2-
plugfalsemoziyuruwo-tong-rishi-tutemita
- 5. コントローラ内でaction/2を
オーバーライドする
以下の例、post_controller.ex内の「
conn.assigns.current_userへの繰り返しアクセス」があ
まりイケてない。current_user(conn)を使っても、不必要な
MAPへのアクセスが増えてベストではない。
defmodule MyApp.PostController do
use MyApp.Web, :controller
def show(conn, %{"id" => id}) do
{:ok, post} =
Blog.get_post_for_user(conn.assigns.current_user, id)
render(conn, "show.html", owner: conn.assigns.current_user,
post: post)
end
def create(conn, %{"post" => post_params}) do
{:ok, post} = Blog.publish_post(conn.assigns.current_user,
post_params)
redirect(conn, to: user_post_path(conn,
conn.assigns.current_user, post)
end
end
- 6. コントローラ内でaction/2を
オーバーライドする
以下のように action/2をオーバーライド
defmodule MyApp.PostController do
use MyApp.Web, :controller
def action(conn, _) do
args = [conn, conn.params, conn.assigns[:current_user] ||
:guest]
apply(__MODULE__, action_name(conn), args)
end
def show(conn, %{"id" => id}, current_user) do
{:ok, post} = Blog.get_post_for_user(current_user, id)
render(conn, "show.html", owner: current_user, post: post)
end
def create(conn, %{"post" => post_params}, current_user) do
{:ok, post} = Blog.publish_post(current_user, post_params)
redirect(conn, to: user_post_path(conn, current_user, post)
end
end
- 8. モジュール内で関数をCallす
るには
• 該当モジュール内の def または defp で定義
• import宣言により、他のモジュールからインポート
• use宣言により、該当モジュール内のdef か defp か import か
use か @before_compile に展開
• 該当モジュール内の @before_compile 属性で指定したマク
ロが def か defp か import か use に展開
- 9. ErrorViewはコントローラで
直接レンダリングする
with/elseを使えば、ステータスコードに応じて、テンプレートで
はなく、コントローラ内でエラー画面を出し分けられる(よくあるの
は、「Ecto.NoResultsError」を "404.html"テンプレート
で、 「Phoenix.ActionClauseError」を “400.html"テ
ンプレートで 等)
def create(conn, %{"post" => post_params}, current_user) do
with {:ok, post} <- Blog.publish_post(current_user, post_params) do
redirect(conn, to: user_post_path(conn, current_user, post)
else
{:error, %Ecto.Changeset{} = changeset} -> render(conn, "edit.html",
changeset: changeset)
{:error, :unauthorized} ->
conn
|> put_status(401)
|> render(ErrorView, :"401", message: "You are not authorized to
publish posts")
{:error, :rate_limited} ->
conn
|> put_status(429)
|> render(ErrorView, :"429", message: "You have exceeded the max
allowed posts for today")
end
end
- 11. Task.awaitする必要がなけれ
ば、Task.asyncを使わない
以下がNGパターン
def delete(conn, _, current_user) do
{:ok, user} = Accounts.cancel_account(current_user)
Task.async(fn -> Audits.alert_cancellation_notice(user) end)
conn
|> signout()
|> put_flash(:info, "So sorry to see you go!")
|> redirect(to: "/")
end
TaskのCallerとTaskがリンクされているので、両側における異常
終了が双方に影響する。クライアントは、アカウントが削除された直後
に500エラーを受け取り、きちんとオペレーションが成功したかどうか
が分からないなど、問題がある