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