ConnからSocketにコンバートする方法+phx.gen.authのログイン処理に関するメモ
Socketは生成されるときに、Connをもとに生成される。
具体的にどのように記述するべきなのか調べたメモ。
ConnとSocketとは
Plug.Conn
はHTTPリクエストを受信したときに生成される構造体である。
リクエストとレスポンスに用いられ、リクエスト用のフィールドと
レスポンス用のフィールド、またPhoenix側で値を格納するフィールドを含んでいる。
またPlug.Conn
(モジュール)自体はリクエストとレスポンスを操作するためのモジュールであり、後述するPlug.Sessionにアクセスするためにも使用する。
Phoenix.LiveView.Socket
はliveルートにHTTPリクエストを受信したときに生成される構造体である。
これが状態と呼ばれるものであり、ライフサイクル上で必要なデータを格納している。
Plug.Session
はルートに到達したときに生成される構造体である。セッション管理に使用される。
コンバートの方法
このような場合、まずlive_session
というマクロでliveセッションを作成する必要がある。
liveセッションとは、いくつかのliveルートをグループ化するものである
このマクロにはon_mount
というオプションがある。
on_mount
コールバックを実装したモジュールと第一引数のアトムを、次の形式で指定する。(パターンマッチで処理を分岐させている)
よってlive_session
マクロは次の形式で記述する。
live_session セッション名,
on_mount: [{モジュール, アトム}] do
ルート
...
end
liveセッション内で必要となる共通のデータを状態に追加する処理などを実行することができる。
live_session
は次のようにscope
内に定義することができる。
live_session :return_to_home_live_index_required_authentication,
on_mount: [{SampleWeb.AccountAuth, :ensure_authenticated}] do
live "/", HomeLive.Index, :index
end
なお、liveセッション名は理解しやすい名前にすること。
on_mount
の実装
on_mount
の実装は次の形式で記述する。
def on_mount(アトム, params, session, socket) do
...
end
戻り値は、次のタプルとなる。
-
{:cont, socket}
- このままsocketを渡し、mountを開始する。 -
{:halt, socket}
- mountを中止する。リダイレクトを行う場合はこちらを返すようにする。
実装は例のようになる。
def on_mount(アトム, _params, _session, socket) do
case :takashi do
:takashi -> {:cont, assign(socket, :name, "takashi")}
:tanuki -> {:halt, Phoenix.LiveView.redirect(socket, to: ~p"/")}
end
end
データの受け渡し
connのデータをsocketに渡すためには、pipe_through(scopeの固有の処理)でPlug.session
への値の挿入を行う必要がある。
次のput_takashi
という処理でデータの受け渡しを行うものとする。
pipe_through [:browser, put_takashi]
以下のように定義されている。
def put_takashi(conn, _opts) do
put_session(conn, :takashi, "takashi")
end
処理が実行されると、Plug.Conn
のprivate
フィールドにある:plug_session
というキーの値であるmapにtakashi: "takashi"
が挿入される。
private: %{ :plug_session => %{..., takashi: "takashi"}, ...}
この:plug_session
の値はLiveViewのmount
第2引数で参照することができる。
def mount(_params, session, socket) do
IO.puts "session: #{inspect session}"
...
end
IO.puts
の出力は以下のようになる。
session: %{..., "takashi" => "takashi"}
sessionに値を渡すことができたので、次はassignsに渡す。
sessionから値を取得するだけ。
def on_mount(アトム, _params, session, socket) do
{:cont, assign(socket, :name, session[:takashi])
end
結論
ルートにリクエストを受信する前にsocketにデータを追加したい場合、次の手順を実行する。
-
scope
にlive_session
マクロの記述をする。 - liveセッション名と実行する
on_mount
を{モジュール名, アトム}
で指定する。 -
on_mount
コールバックを実装して、on_mount:
に指定する。 -
do .. end
にルートをliveルートを記述する。
phx.gen.authのログイン処理に関するメモ
以下のスコープがあるとする。
scope "/", SampleWeb do
pipe_through [:browser, :require_authenticated_account]
live_session :require_authenticated_account,
on_mount: [{SampleWeb.AccountAuth, :ensure_authenticated}] do
live "/accounts/settings", AccountSettingsLive, :edit
live "/accounts/settings/confirm_email/:token", AccountSettingsLive, :confirm_email
end
end
スコープ内に記述されていることを要約すると次のようになる。
- このスコープは内のliveルートはログインを必要とする。ログインしていない場合、ログインページにリダイレクトする。
-
live_session
マクロに記述しているliveルートは/accounts/settings/以下略
はログインを必要とする。ログインしていないとき、ログインページにリダイレクトする。
疑問に思うことは次のようなことである。
「どちらもログインしていない場合はログインページにリダイレクトするので、require_authenticated_account
を実行しない場合、どのような動作をするのか」
調査結果
require_authenticated_account
の処理の内容は、conn.assigns[:current_account]
の存在するかどうかにより次の動作を行う。
存在する場合:
何もしない
存在しない場合:
flashメッセージを表示して、現在のパスをセッションに追加し、ログインページにリダイレクト。
on_mount(:ensure_authenticated, ...)
の処理の内容は、session.assigns.current_account
の存在するかどうかにより次の動作を行う。
存在する場合:
socketに追加するだけ。
存在しない場合:
flashメッセージを表示して、ログインページにリダイレクト。
大きく異なる点は、ログイン後にリダイレクト前のページに戻れるかどうかである。
つまり、該当ルートに到達したときの処理は次のようになる。
- ログインしていないので
require_authenticated_account
でログインページにリダイレクト
- このとき
maybe_store_return_to(%{method: "GET"} = conn)
によってsessionの:account_return_to
というキーの値に現在のパスが格納される
- ログインページで入力を行う
- 入力(構造体のインスタンス)が
AccountSessionController, :create
のparamsに渡される
- 実行されるアクション内で
AccountAuth.log_in_account
が呼び出される
- sessionを初期化した後、予め取得した
:account_return_to
の値(リダイレクト前パス)にリダイレクト(or/
に飛ぶ)
よって:require_authenticated_accoun
はログイン後にもとのページに戻るための処理であるので、実行しない場合はログイン後に/
にリダイレクトする(signed_in_path()
に記述されている)。
なお、connにcurrent_accountを追加する処理は、pipelineに追加されているfetch_current_account
によるものである。
以下のような記述は、on_mount
側でログインを確認するまでもないので、on_mount(:mount_current_account)
を実行しても良いということになる。
pipe_through [:browser, :require_authenticated_account]
...
on_mount: [{SampleWeb.AccountAuth, :mount_current_account}] do
...
end