0

私は Phoenix 1.6 アプリに取り組んでいます。動作していたGitHub経由の認証にUeberauthを使用しました。認証されたユーザーができる、トピックとコメントを投稿するためのチャネルをセットアップしました。次に、チャネルで使用するユーザー トークンを追加してみました。mix phx.new.socket Userを使用して作成された定型コードの指示に従っていました。user_socket.jsファイルには、テンプレートでトークンを作成するための指示があり、機能しました。トークンは、connect関数のuser_socket.exで検証されます。トークンをconnに追加するために、router.exに新しいプラグput_user_tokenを作成しました。、これも機能しました。しかし、プラグ内のロジックに問題がありました。これは私のコードです:

  defp put_user_token(conn, _) do
    if conn.assigns.user do
      token = Phoenix.Token.sign(conn, "user socket", conn.assigns.user.id)
      assign(conn, :user_token, token)
    else
      conn
    end

これは、ログインしている限り機能します。問題は、ログアウトして再度ログインしようとしたときです。プラグ内の if ステートメントのボイラープレート コードは次のとおりです。

if current_user = conn.assigns[:current_user] do

現在のユーザーがuserとして定義されており、値がデータベース内のユーザー レコードの ID と等しいため、私のコードは異なります。問題は、Ueberauth をセットアップするときに、 put_user_tokenプラグの前に別のプラグSetUserを作成したことだと思います。これは SetUser の呼び出し関数です。

 def call(conn, _opts) do
        user_id = get_session(conn, :user_id)

        cond do
            user = user_id && Repo.get(User, user_id) ->
                assign(conn, :user, user)
            true ->
                assign(conn, :user, nil)
        end
    end

ログアウトした後、アプリがホームページにリダイレクトされ、プラグが実行され、 conn.assigns.userの値がnilに設定されているようです。その後、エラーが発生し、再度ログインできません。

put_user_tokenプラグのifステートメントでnil値を処理できるようにする方法を考える必要があります。is_integer(conn.assigns.user)と他のいくつかの比較を試しましたが、 nilの値が存在する場合、アプリはクラッシュします。

4

1 に答える 1

0

この質問は、単純化できれば答えやすいと思います。問題を単純化すると、ワイヤーが交差している場所が明確になると思います。ただし、いくつかの説明を試みさせてください。

まず、ifElixir ではステートメントはいくぶん一義的です。ほとんどの場合、ステートメントなしで実行フローを定義でき、ステートメントに依存しないコードの方が読みやすいことがわかりますif

if関連して、値の「真実性」をチェックするために使用する場合は、十分に注意してください。これは Elixir だけの問題ではありません。この動作は、どの言語でも問題になる可能性があります。たとえば、あるバージョンでは空のオブジェクトを false と評価し、別のバージョンでは true と評価した PHP を覚えているようです (!!)。Elixir では、0または空のオブジェクトはどちらも「真実」ですが、真実ではありません

たとえば、次のコードをリファクタリングすることを検討してください。

defp put_user_token(conn, _) do
  if conn.assigns.user do
    token = Phoenix.Token.sign(conn, "user socket", conn.assigns.user.id)
    assign(conn, :user_token, token)
  else
    conn
  end
end

より明確なものに、おそらく:

defp put_user_token(conn, _) do
  case conn.assigns.user do
    nil -> conn
    user -> 
      token = Phoenix.Token.sign(conn, "user socket", conn.assigns.user.id)
      assign(conn, :user_token, token)
  end
end

または、次のように、パターン マッチングを関数シグネチャにまでプッシュすることを検討してください ( の正確な形状はconnわかりませんが、アイデアが得られることを願っています)。

defp put_user_token(%{assigns: %{user: nil}} = conn, _), do: conn
defp put_user_token(%{assigns: %{user: user}} = conn, _) do 
  token = Phoenix.Token.sign(conn, "user socket", conn.assigns.user.id)
  assign(conn, :user_token, token)
end

:is_logged_in?MAYBE がマップ/構造体であるか、MAYBE が nil であるかを一連のチェックを行うと混乱する可能性があり、読みにくいため、コードのピボット ポイントのような単純なブール値を割り当てる方が簡単な場合があります。 .

最後に、ユーザー データを取得するこのコードの反対側を再確認します。

def call(conn, _opts) do
  case get_session(conn, :user_id) do
    nil -> conn
    user_id -> user = Repo.get(User, user_id)
      assign(conn, :user, user)
  end
end

または、少し厳密にして、セッションのユーザー ID がデータベースに存在しない可能性を処理するには、次のwithようなステートメントとしてこれをリファクタリングできます。

def call(conn, _opts) do
  with user_id when !is_nil(user_id) <- get_session(conn, :user_id) 
   user when !is_nil(user) <- Repo.get(User, user_id)
      assign(conn, :user, user)
  else
    _ -> conn
  end
end

コンポーネントのステップを、より明示的なものを返す独自の名前付きプライベート関数に移動すると、読みやすくなると思いますnil

また、ここでフローを再評価します。リクエストごとにデータベースにヒットする必要がある場合、アプリはうまく機能しません。ログインに成功した後にのみ、必要なユーザーデータをセッションに書き込む必要があります。

すべてのコード サンプルはテストされていません。

于 2022-02-28T13:31:07.013 に答える