ConnからSocketにコンバートする方法+phx.gen.authのログイン処理に関するメモ

- [ConnからSocketにコンバートする方法+phx.gen.authのログイン処理に関するメモ](#connからsocketにコンバートする方法phxgenauthのログイン処理に関するメモ)
  - [ConnとSocketとは](#connとsocketとは)
  - [コンバートの方法](#コンバートの方法)
    - [`on_mount`の実装](#on_mountの実装)
  - [データの受け渡し](#データの受け渡し)
  - [結論](#結論)
  - [phx.gen.authのログイン処理に関するメモ](#phxgenauthのログイン処理に関するメモ)
    - [調査結果](#調査結果)


# ConnからSocketにコンバートする方法+phx.gen.authのログイン処理に関するメモ

Socketは生成されるときに、Connをもとに生成される。

具体的にどのように記述するべきなのか調べたメモ。
## ConnとSocketとは

<font color="red">`Plug.Conn`</font>は**HTTPリクエストを受信したとき**に生成される構造体である。
リクエストとレスポンスに用いられ、[リクエスト用のフィールド](https://hexdocs.pm/plug/1.14.2/Plug.Conn.html#module-request-fields)と
[レスポンス用のフィールド](https://hesessionのxdocs.pm/plug/1.14.2/Plug.Conn.html#module-response-fields)、またPhoenix側で値を格納するフィールドを含んでいる。

また`Plug.Conn`(モジュール)自体は**リクエストとレスポンスを操作するためのモジュール**であり、後述するPlug.Sessionにアクセスするためにも使用する。

<font color="red">`Phoenix.LiveView.Socket`</font>は**liveルートにHTTPリクエストを受信したとき**に生成される構造体である。
これが<font color="red">状態</font>と呼ばれるものであり、ライフサイクル上で[必要なデータ](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.Socket.html#t:t/0)を格納している。

<font color="red">`Plug.Session`</font>はルートに到達したときに生成される構造体である。セッション管理に使用される。

## コンバートの方法

このような場合、まず<font color="red">`live_session`</font>というマクロで<font color="red">liveセッション</font>を作成する必要がある。

liveセッションとは、いくつかのliveルートを**グループ化するもの**である

このマクロには<font color="red">`on_mount`</font>というオプションがある。

`on_mount`コールバックを実装した**モジュール**と第一引数の**アトム**を、次の形式で指定する。(パターンマッチで処理を分岐させている)

よって`live_session`マクロは次の形式で記述する。

```
live_session セッション名,
  on_mount: [{モジュール, アトム}] do
    ルート
    ...
  end
```

liveセッション内で必要となる<font color="red">共通のデータを状態に追加する処理</font>などを実行することができる。

`live_session`は次のように`scope`内に定義することができる。

```elixir
live_session :return_to_home_live_index_required_authentication,
  on_mount: [{SampleWeb.AccountAuth, :ensure_authenticated}] do
    live "/", HomeLive.Index, :index
  end
```

なお、liveセッション名は理解しやすい名前にすること。

https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.Router.html#live_session/3

### `on_mount`の実装

`on_mount`の実装は次の形式で記述する。

```
def on_mount(アトム, params, session, socket) do
  ...
end
```

戻り値は、次のタプルとなる。

* `{:cont, socket}` - このままsocketを渡し、mountを開始する。
* `{:halt, socket}` - mountを中止する。<font color="red">リダイレクトを行う場合</font>はこちらを返すようにする。

実装は例のようになる。

```elixir
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
```

https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#on_mount/1

## データの受け渡し

connのデータをsocketに渡すためには、pipe_through(scopeの固有の処理)で`Plug.session`への値の挿入を行う必要がある。

次の`put_takashi`という処理でデータの受け渡しを行うものとする。

```elixir
pipe_through [:browser, put_takashi]
```

以下のように定義されている。

```elixir
def put_takashi(conn, _opts) do
  put_session(conn, :takashi, "takashi")
end
```
処理が実行されると、`Plug.Conn`の`private`フィールドにある<font color="red">`:plug_session`</font>というキーの値である**map**に`takashi: "takashi"`が挿入される。

```
private: %{ :plug_session => %{..., takashi: "takashi"}, ...}
```

この`:plug_session`の値はLiveViewの`mount`第2引数で参照することができる。

```elixir
def mount(_params, session, socket) do
  IO.puts "session: #{inspect session}"
  ...
end
```

`IO.puts`の出力は以下のようになる。

```
session: %{..., "takashi" => "takashi"}
```

sessionに値を渡すことができたので、次はassignsに渡す。

sessionから値を取得するだけ。

```elixir
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のログイン処理に関するメモ

以下のスコープがあるとする。

```elixir
  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メッセージを表示して、ログインページにリダイレクト。

大きく異なる点は、ログイン後に**リダイレクト前のページに戻れるかどうか**である。

つまり、該当ルートに到達したときの処理は次のようになる。

1. ログインしていないので`require_authenticated_account`でログインページにリダイレクト
  * このとき`maybe_store_return_to(%{method: "GET"} = conn)`によってsessionの`:account_return_to`というキーの値に現在のパスが格納される
2. ログインページで入力を行う
  * 入力(構造体のインスタンス)が`AccountSessionController, :create`のparamsに渡される
3. 実行されるアクション内で`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)`を実行しても良いということになる。

```elixir
pipe_through [:browser, :require_authenticated_account]
  ...
  on_mount: [{SampleWeb.AccountAuth, :mount_current_account}] do
    ...
  end
```