test "renders form for new sessions", %{conn: conn} do
conn = get conn, Routes.session_path(conn, :new)
# 200 响应,页面上带有“登录”
assert html_response(conn, 200) =~ "登录"
end
运行测试,结果如下:
$ mix test test/tv_recipe_web/controllers/session_controller_test.exs
** (CompileError) test/tv_recipe_web/controllers/session_controller_test.exs:5: undefined function session_path/2
(stdlib) lists.erl:1338: :lists.foreach/2
(stdlib) erl_eval.erl:670: :erl_eval.do_apply/6
(elixir) lib/code.ex:370: Code.require_file/2
(elixir) lib/kernel/parallel_require.ex:57: anonymous fn/2 in Kernel.ParallelRequire.spawn_requires/5
session_path 函数未定义。要怎么定义,在哪定义?
实际上,在前面的章节里,我们已经遭遇过 user_path,但还没有解释过它从哪里来。
Phoenix automatically generates a module Helpers inside your router which contains named helpers to help developers generate and keep their routes up to date.
Helpers are automatically generated based on the controller name.
def controller do
quote do
use Phoenix.Controller
alias TvRecipe.Repo
import Ecto
import Ecto.Query
import TvRecipe.Router.Helpers
import TvRecipe.Gettext
end
end
mix test test/tv_recipe_web/controllers/session_controller_test.exs
Compiling 1 file (.ex)
1) test renders form for new sessions (TvRecipeWeb.SessionControllerTest)
test/tv_recipe_web/controllers/session_controller_test.exs:4
** (ArgumentError) assign @changeset not available in eex template.
Please make sure all proper assigns have been set. If this
is a child template, ensure assigns are given explicitly by
the parent template as they are not automatically forwarded.
Available assigns: [:conn, :view_module, :view_template]
报错是必然的,我们前面草草写就的 new 函数里,只是一行 render "new.html",并没有传递 changeset - 因为我们根本没有 changeset 可以传递。
with changeset data - when information to populate the form comes from a changeset
with connection data - when a form is created based on the information in the connection (aka Plug.Conn)
without form data - when the functions are used directly, outside of a form
我们没有 changeset,但是涉及表单数据,适用第二种。
diff --git a/web/templates/session/new.html.eex b/web/templates/session/new.html.eex
index 9c1f842..1df67cc 100644
--- a/web/templates/session/new.html.eex
+++ b/web/templates/session/new.html.eex
@@ -1,11 +1,5 @@
<h2>登录</h2>
-<%= form_for @changeset, @action, fn f -> %>
- <%= if @changeset.action do %>
- <div class="alert alert-danger">
- <p>Oops, something went wrong! Please check the errors below.</p>
- </div>
- <% end %>
-
+<%= form_for @conn, Routes.session_path(@conn, :create), [as: :session], fn f -> %>
<div class="form-group <%= if f.errors[:email], do: "has-error" %>">
<%= label f, :email, class: "control-label" %>
<%= text_input f, :email, class: "form-control" %>
diff --git a/test/tv_recipe_web/controllers/session_controller_test.exs b/test/tv_recipe_web/controllers/session_controller_test.exs
index 0372448..6835e40 100644
--- a/test/tv_recipe_web/controllers/session_controller_test.exs
+++ b/test/tv_recipe_web/controllers/session_controller_test.exs
@@ -1,9 +1,24 @@
defmodule TvRecipeWeb.SessionControllerTest do
use TvRecipe.ConnCase
+ alias TvRecipe.Repo
+ alias TvRecipe.Users.User
+ @valid_user_attrs %{email: "chenxsan@gmail.com", username: "chenxsan", password: String.duplicate("a", 6)}
+
test "renders form for new sessions", %{conn: conn} do
conn = get conn, Routes.session_path(conn, :new)
# 200 响应,页面上带有“登录”
assert html_response(conn, 200) =~ "登录"
end
+
+ test "login user and redirect to home page when data is valid", %{conn: conn} do
+ user_changeset = User.changeset(%User{}, @valid_user_attrs)
+ # 插入新用户
+ Repo.insert! user_changeset
+ # 用户登录
+ conn = post conn, Routes.session_path(conn, :create), session: @valid_user_attrs
+ # 显示“欢迎你”的消息
+ assert get_flash(conn, :info) == "欢迎你"
+ # 重定向到主页
+ assert redirected_to(conn) == Routes.page_path(conn, :index)
+ end
end
我们的测试结果是:
$ mix test test/tv_recipe_web/controllers/session_controller_test.exs
Compiling 1 file (.ex)
warning: variable "user" is unused
test/tv_recipe_web/controllers/session_controller_test.exs:16
.
1) test login user and redirect to home page when data is valid (TvRecipeWeb.SessionControllerTest)
test/tv_recipe_web/controllers/session_controller_test.exs:13
** (UndefinedFunctionError) function TvRecipeWeb.SessionController.create/2 is undefined or private
```
`TvRecipeWeb.SessionController.create` 未定义。
打开 `session_controller.ex` 文件,添加 `create` 动作:
```elixir
diff --git a/web/controllers/session_controller.ex b/web/controllers/session_controller.ex
index 66a5304..40ad02f 100644
--- a/web/controllers/session_controller.ex
+++ b/web/controllers/session_controller.ex
@@ -1,7 +1,20 @@
defmodule TvRecipeWeb.SessionController do
use TvRecipeWeb, :controller
+ alias TvRecipe.Repo
+ alias TvRecipe.Users.User
def new(conn, _params) do
render conn, "new.html"
end
+
+ def create(conn, %{"session" => %{"email" => email, "password" => password}}) do
+ # 根据邮箱地址从数据库中查找用户
+ user = Repo.get_by(User, email: email)
+ cond do
+ # 用户存在,且密码正确
+ user && Comeonin.Bcrypt.checkpw(password, user.password_hash) ->
+ conn
+ |> put_flash(:info, "欢迎你")
+ |> redirect(to: Routes.page_path(conn, :index))
+ end
+ end
end
scope "/", TvRecipe do
pipe_through :browser # Use the default browser stack
get "/", PageController, :index
resources "/users", UserController
get "/sessions/new", SessionController, :new
post "/sessions/new", SessionController, :create
end
diff --git a/web/controllers/auth.ex b/web/controllers/auth.ex
index 84b17f7..994112d 100644
--- a/web/controllers/auth.ex
+++ b/web/controllers/auth.ex
@@ -10,6 +10,8 @@ defmodule TvRecipe.Auth do
end
def call(conn, repo) do
+ user_id = get_session(conn, :user_id)
+ user = user_id && repo.get(TvRecipe.Users.User, user_id)
assign(conn, :current_user, user)
end
最后,将 Auth plug 加入 :browser pipeline 中:
diff --git a/lib/tv_recipe_web/router.ex b/lib/tv_recipe_web/router.ex
index e0406d2..1265c86 100644
--- a/lib/tv_recipe_web/router.ex
+++ b/lib/tv_recipe_web/router.ex
@@ -7,6 +7,7 @@ defmodule TvRecipeWeb.Router do
plug :fetch_flash
plug :protect_from_forgery
plug :put_secure_browser_headers
+ plug TvRecipeWeb.Auth, repo: TvRecipe.Repo
end
pipeline :api do
现在运行测试:
$ mix test
...................................
Finished in 0.4 seconds
35 tests, 0 failures
全部通过。
你可能会问,为什么在登录时,不直接保存 user 数据到 session 中,而是保存了 user.id 的数据?假如我们保存了 user 数据,而用户又修改了个人信息,会导致 session 中的 user 数据与数据库中不一致,所以我们只存了 id,然后根据 id 从数据库中读取 user 数据,保证了数据的有效性。
我们来看 的文档,其中 Helpers 一节有说明如下:
怎么办?来看看 Phoenix.HTML.Form 的描述的 form_for 的三种应用场景:
根据 form_for 的,我们将 new.html.eex 做以下修改:
alias TvRecipe.{Repo, User} - 允许我们给模块设置别名,这样可以减少后期输入,不必写完整的 TvRecipe.Repo 与 TvRecipe.User。
我们可以通过。用户第一次访问网站时,服务端会分配一个唯一的 session id,这样每次请求进来,服务端解析 session id 就能知道是谁。听起来很复杂?不必担心,因为 Phoenix 已经帮我们打理好。我们只要关心 session 的存储、读取等就好。