Elixir Phoenix 1.6 - попытка передать токен в браузер, функция nil.id/0 не определена или закрыта

Я работаю над приложением Phoenix 1.6. Я использовал Ueberauth для аутентификации через GitHub, которая работала. Я создал канал для публикации тем и комментариев, которые могут делать авторизованные пользователи. Затем я попытался добавить пользовательский токен для использования в канале. Я следовал инструкциям в стандартном коде, созданном с помощью mix phx.new.socket User . В файле user_socket.js есть инструкции по созданию токена в шаблоне, который сработал. Токен проверяется в user_socket.ex в функции подключения . Я создал новый плагин put_user_token в router.ex , чтобы добавить токен в соединение ., что тоже сработало. Однако у меня возникла проблема с логикой в ​​штекере. Это мой код:

        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 со значением, равным идентификатору записи пользователя в базе данных. Я думаю, проблема в том, что при настройке Ueberauth я создал еще один плагин SetUser , который стоит перед плагином put_user_token . Это функция вызова для 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 . Затем возникает ошибка, и я не могу снова войти в систему.

Мне нужно придумать способ, чтобы оператор if в плагине put_user_token мог обрабатывать нулевое значение. Я попробовал is_integer(conn.assigns.user) и пару других сравнений, но если присутствует значение nil , приложение вылетает.

1 ответ

Я думаю, что на этот вопрос было бы легче ответить, если бы вы могли упростить его — я подозреваю, что как только вы упростите проблему, вам станет ясно, где пересекаются провода. Однако позвольте мне сделать пару уточнений.

Во-первых, операторы в Эликсире несколько однообразны — вы обнаружите, что чаще всего ваши потоки выполнения могут быть определены без них, а код обычно легче читать, когда он не полагается на операторы.

Связанные, будьте очень осторожны при использовании ifдля проверки «правдивости» значения. Это касается не только Эликсира, поведение — это потенциальная ошибка на любом языке. Кажется, я помню PHP, например, который оценивал пустой объект как ложный в одной версии и истинный в другой (!!). В Эликсире, 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?в качестве точки поворота в вашем коде, потому что выполнение множества проверок значения, которое МОЖЕТ быть картой/структурой или МОЖЕТ быть равно нулю, может сбивать с толку и его труднее читать.

Наконец, дважды проверьте другую часть этого кода, где вы извлекаете пользовательские данные:

      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

или быть немного строгим и обрабатывать возможность того, что идентификатор пользователя в сеансе не существует в базе данных, вы можете реорганизовать это как 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.

Я бы также воспользовался моментом, чтобы переоценить поток здесь — приложение не будет работать хорошо, если вам нужно обращаться к базе данных для каждого запроса. Вероятно, вам следует записывать необходимые пользовательские данные в сеанс только после успешного входа в систему.

Все примеры кода не тестировались.

Другие вопросы по тегам