diff --git a/00-prepare/00-prepare.md b/00-prepare/00-prepare.md
index 50efe2b..3555d4c 100644
--- a/00-prepare/00-prepare.md
+++ b/00-prepare/00-prepare.md
@@ -47,7 +47,7 @@ $ mix local.hex --force
## 安装 Phoenix
```bash
-$ mix archive.install https://github.com/phoenixframework/archives/raw/master/phx_new.ez
+mix archive.install hex phx_new 1.5.9
```
## 安装 Node.js(>=5.0.0)
diff --git a/04-user-register/00-prepare.md b/04-user-register/00-prepare.md
index e88689a..e581689 100644
--- a/04-user-register/00-prepare.md
+++ b/04-user-register/00-prepare.md
@@ -42,32 +42,35 @@
但这样的手动添加过程太麻烦,还容易出错,应该有便捷的方法。
-是的,Phoenix 提供了一系列的 mix 工具包。我们要接触的这个是 [`mix phoenix.gen.html`](https://hexdocs.pm/phoenix/Mix.Tasks.Phoenix.Gen.Html.html)。
+是的,Phoenix 提供了一系列的 mix 工具包。我们要接触的这个是 [`mix phx.gen.html`](https://hexdocs.pm/phoenix/Mix.Tasks.Phoenix.Gen.Html.html)。
-请在命令行窗口下切换到 `tv_recipe` 目录,然后执行 `mix phoenix.gen.html` 命令:
+请在命令行窗口下切换到 `tv_recipe` 目录,然后执行 `mix phx.gen.html` 命令:
```
$ cd tv_recipe
-$ mix phoenix.gen.html User users username:string:unique email:string:unique password:string
+$ mix phx.gen.html Users User users username:string:unique email:string:unique password:string
```
-
+
执行命令后的输出如下:
```bash
-* creating web/controllers/user_controller.ex
-* creating web/templates/user/edit.html.eex
-* creating web/templates/user/form.html.eex
-* creating web/templates/user/index.html.eex
-* creating web/templates/user/new.html.eex
-* creating web/templates/user/show.html.eex
-* creating web/views/user_view.ex
-* creating test/controllers/user_controller_test.exs
-* creating web/models/user.ex
-* creating test/models/user_test.exs
+* creating lib/tv_recipe_web/controllers/user_controller.ex
+* creating lib/tv_recipe_web/templates/user/edit.html.eex
+* creating lib/tv_recipe_web/templates/user/form.html.eex
+* creating lib/tv_recipe_web/templates/user/index.html.eex
+* creating lib/tv_recipe_web/templates/user/new.html.eex
+* creating lib/tv_recipe_web/templates/user/show.html.eex
+* creating lib/tv_recipe_web/views/user_view.ex
+* creating test/lib/tv_recipe_web/controllers/user_controller_test.exs
+* creating lib/tv_recipe/users/user.ex
* creating priv/repo/migrations/20170123145857_create_user.exs
+* creating lib/tv_recipe/users.ex
+* injecting lib/tv_recipe/users.ex
+* creating test/lib/tv_recipe/users_test.exs
+* injecting test/tv_recipe/users_test.exs
-Add the resource to your browser scope in web/router.ex:
+Add the resource to your browser scope in lib/tv_recipe_web/router.ex:
resources "/users", UserController
@@ -122,13 +125,13 @@ Generated tv_recipe app
11:08:12.067 [info] == Migrated in 0.0s
```
-操作完上述两步后,因为某些编辑器可能导致的代码重载问题,你需要重启 Phoenix 服务器 - 按两次 Ctrl-C,然后重新执行 `mix phoenix.server`。
+操作完上述两步后,因为某些编辑器可能导致的代码重载问题,你需要重启 Phoenix 服务器 - 按两次 Ctrl-C,然后重新执行 `mix phx.server`。
之后在浏览器中打开网址 `http://localhost:4000/users/new`:

-有了。是不是很惊讶?我们用 `mix phoenix.gen.html` 命令生成的样板,功能已经很完善:增删改查功能全都有了。我们需要的,只是在样板基础上做点修改。
+有了。是不是很惊讶?我们用 `mix phx.gen.html` 命令生成的样板,功能已经很完善:增删改查功能全都有了。我们需要的,只是在样板基础上做点修改。
[接下来](/04-user-register/01-username-required.md)几章,我们将一步步完成本章开头列出的限制条件。
diff --git a/04-user-register/01-username-required.md b/04-user-register/01-username-required.md
index 045b1ce..15a0509 100644
--- a/04-user-register/01-username-required.md
+++ b/04-user-register/01-username-required.md
@@ -1,6 +1,6 @@
# username 必填
-[上一章](/04-user-register/00-prepare.md)里,我们用 `mix phoenix.gen.html` 命令创建出完整用户界面,并且具备增加、删除、更改、查询用户的功能。
+[上一章](/04-user-register/00-prepare.md)里,我们用 `mix phx.gen.html` 命令创建出完整用户界面,并且具备增加、删除、更改、查询用户的功能。
这一章,我们将实现 `username` 的第一个规则:`username` 必填,如果未填写,提示用户`请填写`。
@@ -20,7 +20,7 @@
让我们加上试试:
```elixir
-diff --git a/web/models/user.ex b/web/models/user.ex
+diff --git a/tv_recipe/users/user.ex b/tv_recipe/users/user.ex
index b7713a0..87ce321 100644
--- a/web/models/user.ex
+++ b/web/models/user.ex
@@ -43,7 +43,7 @@ index b7713a0..87ce321 100644
又或者,我们可以用 Phoenix 生成的测试文件来验证。
-打开 `test/models/user_test.exs` 文件,默认内容如下:
+打开 `test/tv_recipe/users_test.exs` 文件,默认内容如下:
```elixir
defmodule TvRecipe.UserTest do
@@ -68,10 +68,10 @@ end
文件中有两个变量,`@valid_attrs` 表示有效的 `User` 属性,`@invalid_attrs` 表示无效的 `User` 属性,我们按本章开头拟定的规则修改 `@valid_attrs`:
```elixir
-diff --git a/test/models/user_test.exs b/test/models/user_test.exs
+diff --git a/test/tv_recipe/users_test.exs b/test/tv_recipe/users_test.exs
index 1d5494f..7c73207 100644
---- a/test/models/user_test.exs
-+++ b/test/models/user_test.exs
+--- a/test/tv_recipe/users_test.exs
++++ b/test/tv_recipe/users_test.exs
@@ -3,7 +3,7 @@ defmodule TvRecipe.UserTest do
alias TvRecipe.User
@@ -83,13 +83,13 @@ index 1d5494f..7c73207 100644
test "changeset with valid attributes" do
```
-接着,在 `user_test.exs` 文件中添加一个新测试:
+接着,在 `users_test.exs` 文件中添加一个新测试:
```elixir
diff --git a/test/models/user_test.exs b/test/models/user_test.exs
index 7c73207..4c174ab 100644
---- a/test/models/user_test.exs
-+++ b/test/models/user_test.exs
+--- a/test/tv_recipe/users_test.exs
++++ b/test/tv_recipe/users_test.exs
@@ -15,4 +15,9 @@ defmodule TvRecipe.UserTest do
changeset = User.changeset(%User{}, @invalid_attrs)
refute changeset.valid?
@@ -97,28 +97,39 @@ index 7c73207..4c174ab 100644
+
+ test "username should not be blank" do
+ attrs = %{@valid_attrs | username: ""}
-+ assert {:username, "请填写"} in errors_on(%User{}, attrs)
++ assert %{username: ["请填写"] } = errors_on(%User{}, attrs)
+ end
end
```
这里,`%{@valid_attrs | username: ""}` 是 Elixir 更新映射(Map)的一个方法。
-至于 `errors_on` 函数,它定义在 `tv_recipe/test/support/model_case.ex` 文件中:
+至于 `errors_on/2` 函数,它需要新增在 `test/support/data_case.ex` 文件中:
```elixir
-def errors_on(struct, data) do
- struct.__struct__.changeset(struct, data)
- |> Ecto.Changeset.traverse_errors(&TvRecipe.ErrorHelpers.translate_error/1)
- |> Enum.flat_map(fn {key, errors} -> for msg <- errors, do: {key, msg} end)
+ def errors_on(changeset) do
+ Ecto.Changeset.traverse_errors(changeset, fn {message, opts} ->
+ Regex.replace(~r"%{(\w+)}", message, fn _, key ->
+ opts |> Keyword.get(String.to_existing_atom(key), key) |> to_string()
+ end)
+ end)
+ end
++
++ def errors_on(struct, attrs) do
++ changeset = struct.__struct__.changeset(struct, attrs)
++ errors_on(changeset)
++ end
end
```
+
+是否很吃惊?要知道,如果是在 JavaScript 里写两个同名函数,后一个函数会覆盖前一个的定义,而 Elixir 下,我们可以定义多个同名函数,它们能处理不同的状况,而又互不干扰。
+
它检查给定数据中的错误消息,并返回给我们。
现在在命令行下运行:
```bash
-$ mix test test/models/user_test.exs
+$ mix test test/tv_recipe/users_test.exs
```
结果如下:
diff --git a/04-user-register/02-username-unique.md b/04-user-register/02-username-unique.md
index 59dae2c..8944b8c 100644
--- a/04-user-register/02-username-unique.md
+++ b/04-user-register/02-username-unique.md
@@ -3,13 +3,13 @@
如果你已完成[上一章](/04-user-register/01-username-required.md),你可能已经猜到,这章的规则要怎么写,不过在那之前,还是让我们先写个测试:
```elixir
-diff --git a/test/models/user_test.exs b/test/models/user_test.exs
+diff --git a/test/tv_recipe/users_test.exs b/test/tv_recipe/users_test.exs
index 4c174ab..47df0c7 100644
---- a/test/models/user_test.exs
-+++ b/test/models/user_test.exs
+--- a/test/tv_recipe/users_test.exs
++++ b/test/tv_recipe/users_test.exs
@@ -20,4 +20,13 @@ defmodule TvRecipe.UserTest do
attrs = %{@valid_attrs | username: ""}
- assert {:username, "请填写"} in errors_on(%User{}, attrs)
+ assert %{username: ["请填写"]} = errors_on(%User{}, attrs)
end
+
+ test "username should be unique" do
@@ -22,7 +22,7 @@ index 4c174ab..47df0c7 100644
+ end
end
```
-此时运行 `mix test test/models/user_test.exs`,我们的测试会全部通过。这是因为,我们在执行 `mix phoenix.gen.html` 命令时,指定了 `unique` 给 `username` 字段,这样生成的 `User` 结构里,我们已经有了唯一性的限定规则,如下所示:
+此时运行 `mix test test/tv_recipe/users_test.exs`,我们的测试会全部通过。这是因为,我们在执行 `mix phx.gen.html` 命令时,指定了 `unique` 给 `username` 字段,这样生成的 `User` 结构里,我们已经有了唯一性的限定规则,如下所示:
```elixir
def changeset(struct, params \\ %{}) do
@@ -35,70 +35,38 @@ end
```
但上面的测试里,我们只知道插入同名用户时,Phoenix 会返回错误,至于错误是什么,我们还没有检查。
-还记得前一章里用于检查给定数据的错误的 `errors_on` 函数么?
-
-```elixir
-def errors_on(struct, data) do
- struct.__struct__.changeset(struct, data)
- |> Ecto.Changeset.traverse_errors(&TvRecipe.ErrorHelpers.translate_error/1)
- |> Enum.flat_map(fn {key, errors} -> for msg <- errors, do: {key, msg} end)
-end
-```
-但很可惜,它接收的是一个结构(struct)与映射。而我们现在手头上只有一个 `TvRecipe.Repo.insert(user_changeset)` 返回的 `changset` 可用。
-
-我们要在 `tv_recipe/test/support/model_case.ex` 文件中再定义一个 `errors_on` 函数,这一回,它接收一个 `changeset` 参数:
-
-```elixir
-diff --git a/test/support/model_case.ex b/test/support/model_case.ex
-index 2b9cb59..85006b5 100644
---- a/test/support/model_case.ex
-+++ b/test/support/model_case.ex
-@@ -62,4 +62,10 @@ defmodule TvRecipe.ModelCase do
- |> Ecto.Changeset.traverse_errors(&TvRecipe.ErrorHelpers.translate_error/1)
- |> Enum.flat_map(fn {key, errors} -> for msg <- errors, do: {key, msg} end)
- end
-+
-+ def errors_on(changeset) do
-+ changeset
-+ |> Ecto.Changeset.traverse_errors(&TvRecipe.ErrorHelpers.translate_error/1)
-+ |> Enum.flat_map(fn {key, errors} -> for msg <- errors, do: {key, msg} end)
-+ end
- end
-```
-是否很吃惊?要知道,如果是在 JavaScript 里写两个同名函数,后一个函数会覆盖前一个的定义,而 Elixir 下,我们可以定义多个同名函数,它们能处理不同的状况,而又互不干扰。
-
我们来完善下我们上面的测试代码:
```elixir
-diff --git a/test/models/user_test.exs b/test/models/user_test.exs
+diff --git a/test/tv_recipe/users_test.exs b/test/tv_recipe/users_test.exs
index 47df0c7..9748671 100644
---- a/test/models/user_test.exs
-+++ b/test/models/user_test.exs
+--- a/test/tv_recipe/users_test.exs
++++ b/test/tv_recipe/users_test.exs
@@ -28,5 +28,8 @@ defmodule TvRecipe.UserTest do
# 尝试插入同名用户,应报告错误
assert {:error, changeset} = TvRecipe.Repo.insert(user_changeset)
+
+ # 错误信息为“用户名已被人占用”
-+ assert {:username, "用户名已被人占用"} in errors_on(changeset)
++ assert %{username: ["用户名已被人占用"]} = errors_on(changeset)
end
end
```
-再次运行 `mix test test/models/user_test.exs` 的结果是:
+再次运行 `mix test test/tv_recipe/users_test.exs` 的结果是:
```bash
-$ mix test test/models/user_test.exs
+$ mix test test/tv_recipe/users_test.exs
.
1) test username should be unique (TvRecipe.UserTest)
- test/models/user_test.exs:24
+ test/tv_recipe/users_test.exs:24
Assertion with in failed
- code: {:username, "用户名已被人占用"} in errors_on(changeset)
- left: {:username, "用户名已被人占用"}
+ code: %{username: ["用户名已被人占用"]} = errors_on(changeset)
+ left: %{username: ["用户名已被人占用"]}
right: [username: "has already been taken"]
stacktrace:
- test/models/user_test.exs:33: (test)
+ test/tv_recipe/users_test.exs:33: (test)
..
@@ -109,13 +77,13 @@ Finished in 0.1 seconds
这是当然,我们还未自定义用户名重复时的提示消息。
-打开 `web/models/user.ex` 文件,做如下修改:
+打开 `lib/tv_recipe/users/user.ex` 文件,做如下修改:
```elixir
-diff --git a/web/models/user.ex b/web/models/user.ex
+diff --git a/lib/tv_recipe/users/user.ex b/lib/tv_recipe/users/user.ex
index 87ce321..88ad2af 100644
---- a/web/models/user.ex
-+++ b/web/models/user.ex
+--- a/lib/tv_recipe/users/user.ex
++++ b/lib/tv_recipe/users/user.ex
@@ -16,7 +16,7 @@ defmodule TvRecipe.User do
struct
|> cast(params, [:username, :email, :password])
@@ -136,13 +104,13 @@ index 87ce321..88ad2af 100644
我们先写个测试验证一下:
```elixir
-diff --git a/test/models/user_test.exs b/test/models/user_test.exs
+diff --git a/test/tv_recipe/users_test.exs b/test/tv_recipe/users_test.exs
index 9748671..44cb21b 100644
---- a/test/models/user_test.exs
-+++ b/test/models/user_test.exs
+--- a/test/tv_recipe/users_test.exs
++++ b/test/tv_recipe/users_test.exs
@@ -32,4 +32,13 @@ defmodule TvRecipe.UserTest do
# 错误信息为“用户名已被人占用”
- assert {:username, "用户名已被人占用"} in errors_on(changeset)
+ assert %{username: ["用户名已被人占用"]} = errors_on(changeset)
end
+
+ test "username should be case insensitive" do
@@ -158,14 +126,14 @@ index 9748671..44cb21b 100644
运行测试的结果是:
```bash
-$ mix test test/models/user_test.exs
+$ mix test test/tv_recipe/users_test.exs
warning: variable "changeset" is unused
- test/models/user_test.exs:42
+ test/tv_recipe/users_test.exs:42
...
1) test username should be case insensitive (TvRecipe.UserTest)
- test/models/user_test.exs:36
+ test/tv_recipe/users_test.exs:36
match (=) failed
code: {:error, changeset} = TvRecipe.Repo.insert(another_user_changeset)
right: {:ok,
@@ -175,7 +143,7 @@ warning: variable "changeset" is unused
password: "some content",
updated_at: ~N[2017-01-24 11:57:43.741109], username: "Chenxsan"}}
stacktrace:
- test/models/user_test.exs:42: (test)
+ test/tv_recipe/users_test.exs:42: (test)
.
@@ -197,10 +165,10 @@ Finished in 0.1 seconds
根据提示,我们的 `user.ex` 代码可以做如下修改:
```elixir
-diff --git a/web/models/user.ex b/web/models/user.ex
+diff --git a/lib/tv_recipe/users/user.ex b/lib/tv_recipe/users/user.ex
index 88ad2af..fc07824 100644
---- a/web/models/user.ex
-+++ b/web/models/user.ex
+--- a/lib/tv_recipe/users/user.ex
++++ b/lib/tv_recipe/users/user.ex
@@ -16,6 +16,7 @@ defmodule TvRecipe.User do
struct
|> cast(params, [:username, :email, :password])
@@ -218,7 +186,7 @@ index 88ad2af..fc07824 100644
## 数据库迁移
-在[用户注册一章](00-prepare.md),我们用 `mix phoenix.gen.html` 生成了许多样板文件,其中有一条:
+在[用户注册一章](00-prepare.md),我们用 `mix phx.gen.html` 生成了许多样板文件,其中有一条:
```bash
* creating priv/repo/migrations/20170123145857_create_user.exs
@@ -297,10 +265,10 @@ $ mix ecto.migrate
最后要记得将此前 `user.ex` 文件中 `String.downcase` 的修改撤销掉:
```elixir
-diff --git a/web/models/user.ex b/web/models/user.ex
+diff --git a/lib/tv_recipe/users/user.ex b/lib/tv_recipe/users/user.ex
index fc07824..88ad2af 100644
---- a/web/models/user.ex
-+++ b/web/models/user.ex
+--- a/lib/tv_recipe/users/user.ex
++++ b/lib/tv_recipe/users/user.ex
@@ -16,7 +16,6 @@ defmodule TvRecipe.User do
struct
|> cast(params, [:username, :email, :password])
@@ -314,14 +282,14 @@ index fc07824..88ad2af 100644
再运行测试看看:
```bash
-mix test test/models/user_test.exs
+mix test test/tv_recipe/users_test.exs
warning: variable "changeset" is unused
- test/models/user_test.exs:42
+ test/tv_recipe/users_test.exs:42
.
1) test username should be case insensitive (TvRecipe.UserTest)
- test/models/user_test.exs:36
+ test/tv_recipe/users_test.exs:36
** (Ecto.ConstraintError) constraint error when attempting to insert struct:
* unique: users_lower_username_index
@@ -338,12 +306,12 @@ warning: variable "changeset" is unused
(elixir) lib/enum.ex:1229: Enum."-map/2-lists^map/1-0-"/2
(ecto) lib/ecto/repo/schema.ex:479: Ecto.Repo.Schema.constraints_to_errors/3
(ecto) lib/ecto/repo/schema.ex:213: anonymous fn/13 in Ecto.Repo.Schema.do_insert/4
- test/models/user_test.exs:42: (test)
+ test/tv_recipe/users_test.exs:42: (test)
.
2) test username should be unique (TvRecipe.UserTest)
- test/models/user_test.exs:24
+ test/tv_recipe/users_test.exs:24
** (Ecto.ConstraintError) constraint error when attempting to insert struct:
* unique: users_lower_username_index
@@ -360,7 +328,7 @@ warning: variable "changeset" is unused
(elixir) lib/enum.ex:1229: Enum."-map/2-lists^map/1-0-"/2
(ecto) lib/ecto/repo/schema.ex:479: Ecto.Repo.Schema.constraints_to_errors/3
(ecto) lib/ecto/repo/schema.ex:213: anonymous fn/13 in Ecto.Repo.Schema.do_insert/4
- test/models/user_test.exs:30: (test)
+ test/tv_recipe/users_test.exs:30: (test)
.
@@ -370,10 +338,10 @@ Finished in 0.1 seconds
情况变得更糟糕了,报告了 2 个错误。这是因为索引名称已经改变,而我们的代码还在使用默认的旧索引名。我们需要在 `unique_constraint` 里明确指出索引名称:
```elixir
-diff --git a/web/models/user.ex b/web/models/user.ex
+diff --git a/lib/tv_recipe/users/user.ex b/lib/tv_recipe/users/user.ex
index 88ad2af..08e4054 100644
---- a/web/models/user.ex
-+++ b/web/models/user.ex
+--- a/lib/tv_recipe/users/user.ex
++++ b/lib/tv_recipe/users/user.ex
@@ -16,7 +16,7 @@ defmodule TvRecipe.User do
struct
|> cast(params, [:username, :email, :password])
@@ -387,9 +355,9 @@ index 88ad2af..08e4054 100644
再跑一遍测试:
```bash
-$ mix test test/models/user_test.exs
+$ mix test test/tv_recipe/users_test.exs
warning: variable "changeset" is unused
- test/models/user_test.exs:42
+ test/tv_recipe/users_test.exs:42
.....
@@ -414,15 +382,15 @@ assert {:error, changeset} = TvRecipe.Repo.insert(another_user_changeset)
让我们完善下测试:
```elixir
-diff --git a/test/models/user_test.exs b/test/models/user_test.exs
+diff --git a/test/tv_recipe/users_test.exs b/test/tv_recipe/users_test.exs
index 9451c2d..975c7b1 100644
---- a/test/models/user_test.exs
-+++ b/test/models/user_test.exs
+--- a/test/tv_recipe/users_test.exs
++++ b/test/tv_recipe/users_test.exs
@@ -40,5 +40,6 @@ defmodule TvRecipe.UserTest do
# 尝试插入大小写不一致的用户名,应报告错误
another_user_changeset = User.changeset(%User{}, %{@valid_attrs | username: "Chenxsan", email: "chenxsan+1@gmail.com"})
assert {:error, changeset} = TvRecipe.Repo.insert(another_user_changeset)
-+ assert {:username, "用户名已被人占用"} in errors_on(changeset)
++ assert %{username: ["用户名已被人占用"]} = errors_on(changeset)
end
end
```
diff --git a/04-user-register/03-username-format.md b/04-user-register/03-username-format.md
index db0065b..5820ff4 100644
--- a/04-user-register/03-username-format.md
+++ b/04-user-register/03-username-format.md
@@ -4,16 +4,16 @@
我们用测试来验证一下。
-打开 `test/models/user_test.exs` 文件,添加一个测试:
+打开 `test/tv_recipe/users_test.exs` 文件,添加一个测试:
```elixir
-diff --git a/test/models/user_test.exs b/test/models/user_test.exs
+diff --git a/test/tv_recipe/users_test.exs b/test/tv_recipe/users_test.exs
index 975c7b1..644f4c3 100644
---- a/test/models/user_test.exs
-+++ b/test/models/user_test.exs
+--- a/test/tv_recipe/users_test.exs
++++ b/test/tv_recipe/users_test.exs
@@ -42,4 +42,10 @@ defmodule TvRecipe.UserTest do
assert {:error, changeset} = TvRecipe.Repo.insert(another_user_changeset)
- assert {:username, "用户名已被人占用"} in errors_on(changeset)
+ assert %{username: ["用户名已被人占用"]} = errors_on(changeset)
end
+
+ test "username should only contains [a-zA-Z0-9_]" do
@@ -26,15 +26,15 @@ index 975c7b1..644f4c3 100644
命令行下运行测试得到的结果是:
```bash
-mix test test/models/user_test.exs
+mix test test/tv_recipe/users_test.exs
..
1) test username should only contains [a-zA-Z0-9_] (TvRecipe.UserTest)
- test/models/user_test.exs:46
+ test/tv_recipe/users_test.exs:46
Expected false or nil, got true
code: changeset.valid?()
stacktrace:
- test/models/user_test.exs:49: (test)
+ test/tv_recipe/users_test.exs:49: (test)
...
@@ -45,15 +45,15 @@ Finished in 0.1 seconds
显然,我们需要添加一个规则,在哪儿?怎么定义?
-还是在 `web/models/user.ex` 文件中。
+还是在 `lib/tv_recipe/users/user.ex` 文件中。
要限制字符,我们使用 [`validate_format`](https://hexdocs.pm/ecto/Ecto.Changeset.html#validate_format/4):
```elixir
-diff --git a/web/models/user.ex b/web/models/user.ex
+diff --git a/lib/tv_recipe/users/user.ex b/lib/tv_recipe/users/user.ex
index 08e4054..7d7d59f 100644
---- a/web/models/user.ex
-+++ b/web/models/user.ex
+--- a/lib/tv_recipe/users/user.ex
++++ b/lib/tv_recipe/users/user.ex
@@ -16,6 +16,7 @@ defmodule TvRecipe.User do
struct
|> cast(params, [:username, :email, :password])
@@ -70,10 +70,10 @@ index 08e4054..7d7d59f 100644
但我们还要再加一个测试,用于验证用户名格式出错时的提示信息。
```elixir
-diff --git a/test/models/user_test.exs b/test/models/user_test.exs
+diff --git a/test/tv_recipe/users_test.exs b/test/tv_recipe/users_test.exs
index 644f4c3..73fc189 100644
---- a/test/models/user_test.exs
-+++ b/test/models/user_test.exs
+--- a/test/tv_recipe/users_test.exs
++++ b/test/tv_recipe/users_test.exs
@@ -48,4 +48,9 @@ defmodule TvRecipe.UserTest do
changeset = User.changeset(%User{}, attrs)
refute changeset.valid?
@@ -81,7 +81,7 @@ index 644f4c3..73fc189 100644
+
+ test "changeset with invalid username should throw errors" do
+ attrs = %{@valid_attrs | username: "陈三"}
-+ assert {:username, "用户名只允许使用英文字母、数字及下划线"} in errors_on(%User{}, attrs)
++ assert %{username: ["用户名只允许使用英文字母、数字及下划线"]} = errors_on(%User{}, attrs)
+ end
end
```
diff --git a/04-user-register/04-username-length.md b/04-user-register/04-username-length.md
index e38dc9d..05a8731 100644
--- a/04-user-register/04-username-length.md
+++ b/04-user-register/04-username-length.md
@@ -8,38 +8,38 @@
老规矩,先写测试:
```elixir
-diff --git a/test/models/user_test.exs b/test/models/user_test.exs
+diff --git a/test/tv_recipe/users_test.exs b/test/tv_recipe/users_test.exs
index 73fc189..26a7735 100644
---- a/test/models/user_test.exs
-+++ b/test/models/user_test.exs
+--- a/test/tv_recipe/users_test.exs
++++ b/test/tv_recipe/users_test.exs
@@ -53,4 +53,16 @@ defmodule TvRecipe.UserTest do
attrs = %{@valid_attrs | username: "陈三"}
- assert {:username, "用户名只允许使用英文字母、数字及下划线"} in errors_on(%User{}, attrs)
+ assert %{username: ["用户名只允许使用英文字母、数字及下划线"]} = errors_on(%User{}, attrs)
end
+
+ test "username's length should be larger than 3" do
+ attrs = %{@valid_attrs | username: "ab"}
+ changeset = User.changeset(%User{}, attrs)
-+ assert {:username, "用户名最短 3 位"} in errors_on(changeset)
++ assert %{username: ["用户名最短 3 位"]} = errors_on(changeset)
+ end
+
+ test "username's length should be less than 15" do
+ attrs = %{@valid_attrs | username: String.duplicate("a", 16)}
+ changeset = User.changeset(%User{}, attrs)
-+ assert {:username, "用户名最长 15 位"} in errors_on(changeset)
++ assert %{username: ["用户名最长 15 位"]} = errors_on(changeset)
+ end
end
```
显然,我们新增的这两个测试会失败,因为我们还没有加上限制规则。
-打开 `web/models/user.ex` 文件,添加两条规则:
+打开 `lib/tv_recipe/users/user.ex` 文件,添加两条规则:
```elixir
-diff --git a/web/models/user.ex b/web/models/user.ex
+diff --git a/lib/tv_recipe/users/user.ex b/lib/tv_recipe/users/user.ex
index 7d7d59f..8c68e6d 100644
---- a/web/models/user.ex
-+++ b/web/models/user.ex
+--- a/lib/tv_recipe/users/user.ex
++++ b/lib/tv_recipe/users/user.ex
@@ -17,6 +17,8 @@ defmodule TvRecipe.User do
|> cast(params, [:username, :email, :password])
|> validate_required([:username, :email, :password], message: "请填写")
diff --git a/04-user-register/05-username-exclude.md b/04-user-register/05-username-exclude.md
index 9155fe9..afb6e20 100644
--- a/04-user-register/05-username-exclude.md
+++ b/04-user-register/05-username-exclude.md
@@ -5,18 +5,18 @@
我们从测试写起:
```elixir
-diff --git a/test/models/user_test.exs b/test/models/user_test.exs
+diff --git a/test/tv_recipe/users_test.exs b/test/tv_recipe/users_test.exs
index 26a7735..f70d4a1 100644
---- a/test/models/user_test.exs
-+++ b/test/models/user_test.exs
+--- a/test/tv_recipe/users_test.exs
++++ b/test/tv_recipe/users_test.exs
@@ -65,4 +65,9 @@ defmodule TvRecipe.UserTest do
changeset = User.changeset(%User{}, attrs)
- assert {:username, "用户名最长 15 位"} in errors_on(changeset)
+ assert %{username: ["用户名最长 15 位"]} = errors_on(changeset)
end
+
+ test "username should not be admin or administrator" do
-+ assert {:username, "系统保留,无法注册,请更换"} in errors_on(%User{}, %{@valid_attrs | username: "admin"})
-+ assert {:username, "系统保留,无法注册,请更换"} in errors_on(%User{}, %{@valid_attrs | username: "administrator"})
++ assert %{username: ["系统保留,无法注册,请更换"]} = errors_on(%User{}, %{@valid_attrs | username: "admin"})
++ assert %{username: ["系统保留,无法注册,请更换"]} = errors_on(%User{}, %{@valid_attrs | username: "administrator"})
+ end
end
```
@@ -24,10 +24,10 @@ index 26a7735..f70d4a1 100644
然后是添加规则,照例还是在 `user.ex` 文件中:
```elixir
-diff --git a/web/models/user.ex b/web/models/user.ex
+diff --git a/lib/tv_recipe/users/user.ex b/lib/tv_recipe/users/user.ex
index 8c68e6d..35e4d0b 100644
---- a/web/models/user.ex
-+++ b/web/models/user.ex
+--- a/lib/tv_recipe/users/user.ex
++++ b/lib/tv_recipe/users/user.ex
@@ -19,6 +19,7 @@ defmodule TvRecipe.User do
|> validate_format(:username, ~r/^[a-zA-Z0-9_]+$/, message: "用户名只允许使用英文字母、数字及下划线")
|> validate_length(:username, min: 3, message: "用户名最短 3 位")
diff --git a/04-user-register/06-email-rules.md b/04-user-register/06-email-rules.md
index c0b6f24..021ec3c 100644
--- a/04-user-register/06-email-rules.md
+++ b/04-user-register/06-email-rules.md
@@ -15,18 +15,18 @@
首先,添加测试规则,验证 `email` 为空时的错误提示:
```elixir
-diff --git a/test/models/user_test.exs b/test/models/user_test.exs
+diff --git a/test/tv_recipe/users_test.exs b/test/tv_recipe/users_test.exs
index f70d4a1..bae1e57 100644
---- a/test/models/user_test.exs
-+++ b/test/models/user_test.exs
+--- a/test/tv_recipe/users_test.exs
++++ b/test/tv_recipe/users_test.exs
@@ -70,4 +70,9 @@ defmodule TvRecipe.UserTest do
- assert {:username, "系统保留,无法注册,请更换"} in errors_on(%User{}, %{@valid_attrs | username: "admin"})
- assert {:username, "系统保留,无法注册,请更换"} in errors_on(%User{}, %{@valid_attrs | username: "administrator"})
+ assert %{username: ["系统保留,无法注册,请更换"]} = errors_on(%User{}, %{@valid_attrs | username: "admin"})
+ assert %{username: ["系统保留,无法注册,请更换"]} = errors_on(%User{}, %{@valid_attrs | username: "administrator"})
end
+
+ test "email should not be blank" do
+ attrs = %{@valid_attrs | email: ""}
-+ assert {:email, "请填写"} in errors_on(%User{}, attrs)
++ assert %{email: ["请填写"]} = errors_on(%User{}, attrs)
+ end
end
```
@@ -39,18 +39,18 @@ index f70d4a1..bae1e57 100644
我们先添加一个测试:
```elixir
-diff --git a/test/models/user_test.exs b/test/models/user_test.exs
+diff --git a/test/tv_recipe/users_test.exs b/test/tv_recipe/users_test.exs
index bae1e57..67aab23 100644
---- a/test/models/user_test.exs
-+++ b/test/models/user_test.exs
+--- a/test/tv_recipe/users_test.exs
++++ b/test/tv_recipe/users_test.exs
@@ -75,4 +75,9 @@ defmodule TvRecipe.UserTest do
attrs = %{@valid_attrs | email: ""}
- assert {:email, "请填写"} in errors_on(%User{}, attrs)
+ assert %{email: ["请填写"]} = errors_on(%User{}, attrs)
end
+
+ test "email should contain @" do
+ attrs = %{@valid_attrs | email: "ab"}
-+ assert {:email, "邮箱格式错误"} in errors_on(%User{}, attrs)
++ assert %{email: ["邮箱格式错误"]} = errors_on(%User{}, attrs)
+ end
end
```
@@ -60,10 +60,10 @@ index bae1e57..67aab23 100644
下面在 `user.ex` 文件中添加 `validate_format` 验证规则:
```elixir
-diff --git a/web/models/user.ex b/web/models/user.ex
+diff --git a/lib/tv_recipe/users/user.ex b/lib/tv_recipe/users/user.ex
index 35e4d0b..fef942b 100644
---- a/web/models/user.ex
-+++ b/web/models/user.ex
+--- a/lib/tv_recipe/users/user.ex
++++ b/lib/tv_recipe/users/user.ex
@@ -21,6 +21,7 @@ defmodule TvRecipe.User do
|> validate_length(:username, max: 15, message: "用户名最长 15 位")
|> validate_exclusion(:username, ~w(admin administrator), message: "系统保留,无法注册,请更换")
@@ -81,13 +81,13 @@ index 35e4d0b..fef942b 100644
仍是先写测试:
```elixir
-diff --git a/test/models/user_test.exs b/test/models/user_test.exs
+diff --git a/test/tv_recipe/users_test.exs b/test/tv_recipe/users_test.exs
index 67aab23..f6c99e5 100644
---- a/test/models/user_test.exs
-+++ b/test/models/user_test.exs
+--- a/test/tv_recipe/users_test.exs
++++ b/test/tv_recipe/users_test.exs
@@ -80,4 +80,16 @@ defmodule TvRecipe.UserTest do
attrs = %{@valid_attrs | email: "ab"}
- assert {:email, "邮箱格式错误"} in errors_on(%User{}, attrs)
+ assert %{email: ["邮箱格式错误"]} = errors_on(%User{}, attrs)
end
+
+ test "email should be unique" do
@@ -99,7 +99,7 @@ index 67aab23..f6c99e5 100644
+ assert {:error, changeset} = TvRecipe.Repo.insert(User.changeset(%User{}, %{@valid_attrs | username: "samchen"}))
+
+ # 错误信息为“邮箱已被人占用”
-+ assert {:email, "邮箱已被人占用"} in errors_on(changeset)
++ assert %{email: ["邮箱已被人占用"]} = errors_on(changeset)
+ end
end
```
@@ -109,10 +109,10 @@ index 67aab23..f6c99e5 100644
打开 `user.ex` 文件,添加 `message` 如下:
```elixir
-diff --git a/web/models/user.ex b/web/models/user.ex
+diff --git a/lib/tv_recipe/users/user.ex b/lib/tv_recipe/users/user.ex
index fef942b..54e7e4c 100644
---- a/web/models/user.ex
-+++ b/web/models/user.ex
+--- a/lib/tv_recipe/users/user.ex
++++ b/lib/tv_recipe/users/user.ex
@@ -22,6 +22,6 @@ defmodule TvRecipe.User do
|> validate_exclusion(:username, ~w(admin administrator), message: "系统保留,无法注册,请更换")
|> unique_constraint(:username, name: :users_lower_username_index, message: "用户名已被人占用")
@@ -128,13 +128,13 @@ index fef942b..54e7e4c 100644
最后,还有一个测试,是关于 `email` 大小写的,即 `a@b` 与 `A@b` 应当认为是一致的:
```elixir
-diff --git a/test/models/user_test.exs b/test/models/user_test.exs
+diff --git a/test/tv_recipe/users_test.exs b/test/tv_recipe/users_test.exs
index f6c99e5..82dcf6a 100644
---- a/test/models/user_test.exs
-+++ b/test/models/user_test.exs
+--- a/test/tv_recipe/users_test.exs
++++ b/test/tv_recipe/users_test.exs
@@ -92,4 +92,14 @@ defmodule TvRecipe.UserTest do
# 错误信息为“邮箱已被人占用”
- assert {:email, "邮箱已被人占用"} in errors_on(changeset)
+ assert %{email: ["邮箱已被人占用"]} = errors_on(changeset)
end
+
+ test "email should be case insensitive" do
@@ -144,7 +144,7 @@ index f6c99e5..82dcf6a 100644
+ # 尝试插入大小写不一致的邮箱,应报告错误
+ another_user_changeset = User.changeset(%User{}, %{@valid_attrs | username: "samchen", email: "chenXsan@gmail.com"})
+ assert {:error, changeset} = TvRecipe.Repo.insert(another_user_changeset)
-+ assert {:email, "邮箱已被人占用"} in errors_on(changeset)
++ assert %{email: ["邮箱已被人占用"]} = errors_on(changeset)
+ end
end
```
@@ -196,10 +196,10 @@ index f6c99e5..82dcf6a 100644
4. 最后,将新索引的名称赋给 `unique_constraint` 的 `name` 参数:
```elixir
- diff --git a/web/models/user.ex b/web/models/user.ex
+ diff --git a/lib/tv_recipe/users/user.ex b/lib/tv_recipe/users/user.ex
index 54e7e4c..9307a3c 100644
- --- a/web/models/user.ex
- +++ b/web/models/user.ex
+ --- a/lib/tv_recipe/users/user.ex
+ +++ b/lib/tv_recipe/users/user.ex
@@ -22,6 +22,6 @@ defmodule TvRecipe.User do
|> validate_exclusion(:username, ~w(admin administrator), message: "系统保留,无法注册,请更换")
|> unique_constraint(:username, name: :users_lower_username_index, message: "用户名已被人占用")
@@ -212,7 +212,7 @@ index f6c99e5..82dcf6a 100644
再跑一遍测试:
```bash
-$ mix test test/models/user_test.exs
+$ mix test test/tv_recipe/users_test.exs
..............
Finished in 0.2 seconds
diff --git a/04-user-register/07-password-rules.md b/04-user-register/07-password-rules.md
index 4a78cf7..75b3fad 100644
--- a/04-user-register/07-password-rules.md
+++ b/04-user-register/07-password-rules.md
@@ -13,23 +13,23 @@
首先,是添加两个测试:
```elixir
-diff --git a/test/models/user_test.exs b/test/models/user_test.exs
+diff --git a/test/tv_recipe/users_test.exs b/test/tv_recipe/users_test.exs
index 82dcf6a..8689f4e 100644
---- a/test/models/user_test.exs
-+++ b/test/models/user_test.exs
+--- a/test/tv_recipe/users_test.exs
++++ b/test/tv_recipe/users_test.exs
@@ -102,4 +102,14 @@ defmodule TvRecipe.UserTest do
assert {:error, changeset} = TvRecipe.Repo.insert(another_user_changeset)
- assert {:email, "邮箱已被人占用"} in errors_on(changeset)
+ assert %{email: ["邮箱已被人占用"]} = errors_on(changeset)
end
+
+ test "password is required" do
+ attrs = %{@valid_attrs | password: ""}
-+ assert {:password, "请填写"} in errors_on(%User{}, attrs)
++ assert %{password: ["请填写"]} = errors_on(%User{}, attrs)
+ end
+
+ test "password's length should be larger than 6" do
+ attrs = %{@valid_attrs | password: String.duplicate("1", 5)}
-+ assert {:password, "密码最短 6 位"} in errors_on(%User{}, attrs)
++ assert %{password: ["密码最短 6 位"]} = errors_on(%User{}, attrs)
+ end
end
```
@@ -41,10 +41,10 @@ index 82dcf6a..8689f4e 100644
打开 `user.ex` 文件,添加一行 `validate_length`:
```elixir
-diff --git a/web/models/user.ex b/web/models/user.ex
+diff --git a/lib/tv_recipe/users/user.ex b/lib/tv_recipe/users/user.ex
index 9307a3c..3069e79 100644
---- a/web/models/user.ex
-+++ b/web/models/user.ex
+--- a/lib/tv_recipe/users/user.ex
++++ b/lib/tv_recipe/users/user.ex
@@ -23,5 +23,6 @@ defmodule TvRecipe.User do
|> unique_constraint(:username, name: :users_lower_username_index, message: "用户名已被人占用")
|> validate_format(:email, ~r/@/, message: "邮箱格式错误")
diff --git a/04-user-register/08-password-storage.md b/04-user-register/08-password-storage.md
index e72c74c..bfcdced 100644
--- a/04-user-register/08-password-storage.md
+++ b/04-user-register/08-password-storage.md
@@ -19,10 +19,11 @@ index a71d654..3320fc8 100644
+++ b/mix.exs
@@ -19,7 +19,7 @@ defmodule TvRecipe.Mixfile do
def application do
- [mod: {TvRecipe, []},
- applications: [:phoenix, :phoenix_pubsub, :phoenix_html, :cowboy, :logger, :gettext,
-- :phoenix_ecto, :postgrex]]
-+ :phoenix_ecto, :postgrex, :comeonin]]
+ [
+ mod: {TvRecipe.Application, []},
+- extra_applications: [:logger, :runtime_tools]
++ extra_applications: [:logger, :runtime_tools, :comeonin]
+ ]
end
# Specifies which paths to compile per environment.
@@ -30,9 +31,9 @@ index a71d654..3320fc8 100644
{:phoenix_html, "~> 2.6"},
{:phoenix_live_reload, "~> 1.0", only: :dev},
{:gettext, "~> 0.11"},
-- {:cowboy, "~> 1.0"}]
-+ {:cowboy, "~> 1.0"},
-+ {:comeonin, "~> 3.0"}]
+- {:plug_cowboy, "~> 2.0"}
++ {:plug_cowboy, "~> 2.0"},
++ {:comeonin, "~> 3.0"}
end
```
@@ -41,26 +42,26 @@ index a71d654..3320fc8 100644
接着在命令行下执行:
```bash
-$ mix do deps.get, compile
+$ mix do deps.get, deps.compile
```
该命令从远程下载了我们新增的 `comeonin` 依赖并编译。
-那么,怎么确认 `comeonin` 安装成功?之前,我们一直是用 `mix phoenix.server` 命令来启动服务器的,接下来,我们要换一种启动方式:
+那么,怎么确认 `comeonin` 安装成功?之前,我们一直是用 `mix phx.server` 命令来启动服务器的,接下来,我们要换一种启动方式:
```bash
-$ iex -S mix phoenix.server
+$ iex -S mix phx.server
```
区别在哪?我们来看看后者启动后的结果:
```
-$ iex -S mix phoenix.server
+$ iex -S mix phx.server
Erlang/OTP 19 [erts-8.2] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]
[info] Running TvRecipe.Endpoint with Cowboy using http://localhost:4000
Interactive Elixir (1.4.0) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> 25 Jan 09:53:09 - info: compiled 6 files into 2 files, copied 3 in 2.1 sec
```
-看到区别了么?我们用 `iex -S mix phoenix.server` 启动后,可以使用 Elixir 的 [`iex`](http://elixir-lang.org/docs/stable/iex/IEx.html)。
+看到区别了么?我们用 `iex -S mix phx.server` 启动后,可以使用 Elixir 的 [`iex`](http://elixir-lang.org/docs/stable/iex/IEx.html)。
比如,我们可以输入 `Com` 然后按 Tab 键:
@@ -116,10 +117,10 @@ iex(1)> 25 Jan 09:53:09 - info: compiled 6 files into 2 files, copied 3 in 2.1 s
不,我们留着 `password`,但要给它加上 `virtual: true`,表示它是个临时字段,不存储到数据库中:
```elixir
- diff --git a/web/models/user.ex b/web/models/user.ex
+ diff --git a/lib/tv_recipe/users/user.ex b/lib/tv_recipe/users/user.ex
index 3069e79..e60e839 100644
- --- a/web/models/user.ex
- +++ b/web/models/user.ex
+ --- a/lib/tv_recipe/users/user.ex
+ +++ b/lib/tv_recipe/users/user.ex
@@ -4,7 +4,8 @@ defmodule TvRecipe.User do
schema "users" do
field :username, :string
@@ -179,10 +180,10 @@ iex(1)> 25 Jan 09:53:09 - info: compiled 6 files into 2 files, copied 3 in 2.1 s
我们可以在 `changeset` 末尾再加一道工序:
```elixir
-diff --git a/web/models/user.ex b/web/models/user.ex
+diff --git a/lib/tv_recipe/users/user.ex b/lib/tv_recipe/users/user.ex
index e60e839..58447c0 100644
---- a/web/models/user.ex
-+++ b/web/models/user.ex
+--- a/lib/tv_recipe/users/user.ex
++++ b/lib/tv_recipe/users/user.ex
@@ -25,5 +25,6 @@ defmodule TvRecipe.User do
|> validate_format(:email, ~r/@/, message: "邮箱格式错误")
|> unique_constraint(:email, name: :users_lower_email_index, message: "邮箱已被人占用")
@@ -208,10 +209,10 @@ changeset = put_password_hash(changeset)
现在,我们来定义 `put_password_hash` 函数:
```elixir
-diff --git a/web/models/user.ex b/web/models/user.ex
+diff --git a/lib/tv_recipe/users/user.ex b/lib/tv_recipe/users/user.ex
index 58447c0..690a1ed 100644
---- a/web/models/user.ex
-+++ b/web/models/user.ex
+--- a/lib/tv_recipe/users/user.ex
++++ b/lib/tv_recipe/users/user.ex
@@ -27,4 +27,13 @@ defmodule TvRecipe.User do
|> validate_length(:password, min: 6, message: "密码最短 6 位")
|> put_password_hash()
@@ -244,13 +245,13 @@ index 58447c0..690a1ed 100644
当然,我们还要添加一个测试,用 [Comeonin.Bcrypt.checkpw](https://hexdocs.pm/comeonin/Comeonin.Bcrypt.html#checkpw/2) 来保证 `put_password_hash` 函数的结果:
```elixir
-diff --git a/test/models/user_test.exs b/test/models/user_test.exs
+diff --git a/test/tv_recipe/users_test.exs b/test/tv_recipe/users_test.exs
index 8689f4e..6e946b0 100644
---- a/test/models/user_test.exs
-+++ b/test/models/user_test.exs
+--- a/test/tv_recipe/users_test.exs
++++ b/test/tv_recipe/users_test.exs
@@ -112,4 +112,9 @@ defmodule TvRecipe.UserTest do
attrs = %{@valid_attrs | password: String.duplicate("1", 5)}
- assert {:password, "密码最短 6 位"} in errors_on(%User{}, attrs)
+ assert %{password: ["密码最短 6 位"]} = errors_on(%User{}, attrs)
end
+
+ test "password should be hashed" do
@@ -263,7 +264,7 @@ index 8689f4e..6e946b0 100644
运行测试:
```bash
-mix test test/models/user_test.exs
+mix test test/tv_recipe/users_test.exs
.................
Finished in 4.2 seconds
@@ -292,7 +293,7 @@ index 0ff4a98..1743d57 100644
再次运行测试:
```bash
-mix test test/models/user_test.exs
+mix test test/tv_recipe/users_test.exs
.................
Finished in 0.2 seconds
diff --git a/04-user-register/09-optimize-ui.md b/04-user-register/09-optimize-ui.md
index ec23498..c07d2a5 100644
--- a/04-user-register/09-optimize-ui.md
+++ b/04-user-register/09-optimize-ui.md
@@ -28,10 +28,10 @@ Phoenix 生成的 `form.html.eex` 模板里使用了 Bootstrap [样式](https://
但模板中生成的样式与 Bootstrap 的比,差了 `has-error` 这样的 CSS 状态类。我们可以给它补上:
```eex
-diff --git a/web/templates/user/form.html.eex b/web/templates/user/form.html.eex
+diff --git a/lib/tv_recipe_web/templates/user/form.html.eex b/lib/tv_recipe_web/templates/user/form.html.eex
index 5857c33..b047466 100644
---- a/web/templates/user/form.html.eex
-+++ b/web/templates/user/form.html.eex
+--- a/lib/tv_recipe_web/templates/user/form.html.eex
++++ b/lib/tv_recipe_web/templates/user/form.html.eex
@@ -5,19 +5,19 @@
<% end %>
@@ -79,30 +79,30 @@ index 5857c33..b047466 100644
## 控制器的测试
-你可能对 `mix test test/models/user_test.exs` 命令已经烂熟于心。但 `mix test test/controllers/user_controller_test.exs` 呢?
+你可能对 `mix test test/tv_recipe/users_test.exs` 命令已经烂熟于心。但 `mix test test/tv_recipe_web/controllers/user_controller_test.exs` 呢?
-我们在生成用户的样板文件时,曾经生成过一个 `user_controller_test.exs` 文件,让我们运行下 `mix test test/controllers/user_controller_test.exs` 看看结果:
+我们在生成用户的样板文件时,曾经生成过一个 `user_controller_test.exs` 文件,让我们运行下 `mix test test/tv_recipe_web/controllers/user_controller_test.exs` 看看结果:
```bash
-$ mix test test/controllers/user_controller_test.exs
+$ mix test test/tv_recipe_web/controllers/user_controller_test.exs
Compiling 1 file (.ex)
....
1) test updates chosen resource and redirects when data is valid (TvRecipe.UserControllerTest)
- test/controllers/user_controller_test.exs:47
+ test/tv_recipe_web/controllers/user_controller_test.exs:47
** (RuntimeError) expected redirection with status 302, got: 200
stacktrace:
(phoenix) lib/phoenix/test/conn_test.ex:443: Phoenix.ConnTest.redirected_to/2
- test/controllers/user_controller_test.exs:50: (test)
+ test/tv_recipe_web/controllers/user_controller_test.exs:50: (test)
....
2) test creates resource and redirects when data is valid (TvRecipe.UserControllerTest)
- test/controllers/user_controller_test.exs:18
+ test/tv_recipe_web/controllers/user_controller_test.exs:18
** (RuntimeError) expected redirection with status 302, got: 200
stacktrace:
(phoenix) lib/phoenix/test/conn_test.ex:443: Phoenix.ConnTest.redirected_to/2
- test/controllers/user_controller_test.exs:20: (test)
+ test/tv_recipe_web/controllers/user_controller_test.exs:20: (test)
@@ -114,10 +114,10 @@ Finished in 0.3 seconds
显然,从模板文件到现在,我们的代码已经变化,现在测试文件一样需要根据实际情况做调整:
```elixir
-diff --git a/test/controllers/user_controller_test.exs b/test/controllers/user_controller_test.exs
+diff --git a/test/tv_recipe_web/controllers/user_controller_test.exs b/test/tv_recipe_web/controllers/user_controller_test.exs
index 2e08483..95d3108 100644
---- a/test/controllers/user_controller_test.exs
-+++ b/test/controllers/user_controller_test.exs
+--- a/test/tv_recipe_web/controllers/user_controller_test.exs
++++ b/test/tv_recipe_web/controllers/user_controller_test.exs
@@ -2,7 +2,7 @@ defmodule TvRecipe.UserControllerTest do
use TvRecipe.ConnCase
@@ -129,8 +129,8 @@ index 2e08483..95d3108 100644
test "lists all entries on index", %{conn: conn} do
@@ -18,7 +18,7 @@ defmodule TvRecipe.UserControllerTest do
test "creates resource and redirects when data is valid", %{conn: conn} do
- conn = post conn, user_path(conn, :create), user: @valid_attrs
- assert redirected_to(conn) == user_path(conn, :index)
+ conn = post conn, Routes.user_path(conn, :create), user: @valid_attrs
+ assert redirected_to(conn) == Routes.user_path(conn, :index)
- assert Repo.get_by(User, @valid_attrs)
+ assert Repo.get_by(User, @valid_attrs |> Map.delete(:password))
end
@@ -138,8 +138,8 @@ index 2e08483..95d3108 100644
test "does not create resource and renders errors when data is invalid", %{conn: conn} do
@@ -48,7 +48,7 @@ defmodule TvRecipe.UserControllerTest do
user = Repo.insert! %User{}
- conn = put conn, user_path(conn, :update, user), user: @valid_attrs
- assert redirected_to(conn) == user_path(conn, :show, user)
+ conn = put conn, Routes.user_path(conn, :update, user), user: @valid_attrs
+ assert redirected_to(conn) == Routes.user_path(conn, :show, user)
- assert Repo.get_by(User, @valid_attrs)
+ assert Repo.get_by(User, @valid_attrs |> Map.delete(:password))
end
diff --git a/05-session/01-login.md b/05-session/01-login.md
index 55cfcc0..0170214 100644
--- a/05-session/01-login.md
+++ b/05-session/01-login.md
@@ -1,6 +1,6 @@
# 登录
-这一次,我们没有 `mix phoenix.gen.html` 可以用,所以要一步一步写了。
+这一次,我们没有 `mix phx.gen.html` 可以用,所以要一步一步写了。
它的过程,跟[添加帮助文件一章](/02-explore-phoenix/02-explore-phoenix.md)一样。
@@ -10,11 +10,11 @@ Don't panic,错误是指引我们成功的路灯。
## 添加路由
-首先在 `test/controllers` 目录下新建一个 `session_controller_test.exs` 文件:
+首先在 `test/tv_recipe_web/controllers` 目录下新建一个 `session_controller_test.exs` 文件:
```elixir
-defmodule TvRecipe.SessionControllerTest do
- use TvRecipe.ConnCase
+defmodule TvRecipeWeb.SessionControllerTest do
+ use TvRecipeWeb.ConnCase
end
```
@@ -22,7 +22,7 @@ end
```elixir
test "renders form for new sessions", %{conn: conn} do
- conn = get conn, session_path(conn, :new)
+ conn = get conn, Routes.session_path(conn, :new)
# 200 响应,页面上带有“登录”
assert html_response(conn, 200) =~ "登录"
end
@@ -30,8 +30,8 @@ end
运行测试,结果如下:
```bash
-$ mix test test/controllers/session_controller_test.exs
-** (CompileError) test/controllers/session_controller_test.exs:5: undefined function session_path/2
+$ 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
@@ -62,10 +62,10 @@ get "/", PageController, :index
我们回头去看控制器的代码,会在开头处看到这么一行:
```elixir
-use TvRecipe.Web, :controller
+use TvRecipeWeb, :controller
```
-而 `TvRecipe.Web` 是定义在 `web/web.ex` 文件,其中会有这样的内容:
+而 `TvRecipeWeb` 是定义在 `tv_recipe_web/tv_recipe_web.ex` 文件,其中会有这样的内容:
```elixir
def controller do
@@ -81,17 +81,17 @@ use TvRecipe.Web, :controller
end
end
```
-我们看到了 `import TvRecipe.Router.Helpers` 一行,这正是我们在控制器中可以直接使用 `user_path` 等函数的原因 - `use TvRecipe.Web, :controller` 做了准备工作。
+我们看到了 `import TvRecipe.Router.Helpers` 一行,这正是我们在控制器中可以直接使用 `user_path` 等函数的原因 - `use TvRecipeWeb, :controller` 做了准备工作。
现在,我们知道要怎么定义 `session_path` 了。
打开 `router.ex` 文件,添加一个新路由:
```elixir
-diff --git a/web/router.ex b/web/router.ex
+diff --git a/lib/tv_recipe_web/router.ex b/lib/tv_recipe_web/router.ex
index 4ddc1cc..aac327c 100644
---- a/web/router.ex
-+++ b/web/router.ex
+--- a/lib/tv_recipe_web/router.ex
++++ b/lib/tv_recipe_web/router.ex
@@ -18,6 +18,7 @@ defmodule TvRecipe.Router do
get "/", PageController, :index
@@ -102,23 +102,23 @@ index 4ddc1cc..aac327c 100644
运行测试:
```bash
-mix test test/controllers/session_controller_test.exs
+mix test test/tv_recipe_web/controllers/session_controller_test.exs
Compiling 8 files (.ex)
- 1) test renders form for new sessions (TvRecipe.SessionControllerTest)
- test/controllers/session_controller_test.exs:4
- ** (UndefinedFunctionError) function TvRecipe.SessionController.init/1 is undefined (module TvRecipe.SessionController
+ 1) test renders form for new sessions (TvRecipeWeb.SessionControllerTest)
+ test/tv_recipe_web/controllers/session_controller_test.exs:4
+ ** (UndefinedFunctionError) function TvRecipeWeb.SessionController.init/1 is undefined (module TvRecipeWeb.SessionController
is not available)
stacktrace:
- TvRecipe.SessionController.init(:new)
- (tv_recipe) web/router.ex:1: anonymous fn/1 in TvRecipe.Router.match_route/4
+ TvRecipeWeb.SessionController.init(:new)
+ (tv_recipe) lib/tv_recipe_web/router.ex:1: anonymous fn/1 in TvRecipe.Router.match_route/4
(tv_recipe) lib/phoenix/router.ex:261: TvRecipe.Router.dispatch/2
- (tv_recipe) web/router.ex:1: TvRecipe.Router.do_call/2
+ (tv_recipe) lib/tv_recipe_web/router.ex:1: TvRecipe.Router.do_call/2
(tv_recipe) lib/tv_recipe/endpoint.ex:1: TvRecipe.Endpoint.phoenix_pipeline/1
(tv_recipe) lib/tv_recipe/endpoint.ex:1: TvRecipe.Endpoint.call/2
(phoenix) lib/phoenix/test/conn_test.ex:224: Phoenix.ConnTest.dispatch/5
- test/controllers/session_controller_test.exs:5: (test)
+ test/tv_recipe_web/controllers/session_controller_test.exs:5: (test)
@@ -133,8 +133,8 @@ Finished in 0.08 seconds
在 `web/controllers` 目录下新建一个 `session_controller.ex` 文件,内容如下:
```elixir
-defmodule TvRecipe.SessionController do
- use TvRecipe.Web, :controller
+defmodule TvRecipeWeb.SessionController do
+ use TvRecipeWeb, :controller
def new(conn, _params) do
render conn, "new.html"
@@ -146,58 +146,58 @@ end
现在运行测试:
```bash
-mix test test/controllers/session_controller_test.exs
+mix test test/tv_recipe_web/controllers/session_controller_test.exs
Compiling 1 file (.ex)
Generated tv_recipe app
- 1) test renders form for new sessions (TvRecipe.SessionControllerTest)
- test/controllers/session_controller_test.exs:4
- ** (UndefinedFunctionError) function TvRecipe.SessionView.render/2 is undefined (module TvRecipe.SessionView is not ava
+ 1) test renders form for new sessions (TvRecipeWeb.SessionControllerTest)
+ test/tv_recipe_web/controllers/session_controller_test.exs:4
+ ** (UndefinedFunctionError) function TvRecipeWeb.SessionView.render/2 is undefined (module TvRecipeWeb.SessionView is not ava
ilable)
stacktrace:
- TvRecipe.SessionView.render("new.html", %{conn: %Plug.Conn{adapter: {Plug.Adapters.Test.Conn, :...}, assigns: %{layou
+ TvRecipeWeb.SessionView.render("new.html", %{conn: %Plug.Conn{adapter: {Plug.Adapters.Test.Conn, :...}, assigns: %{layou
t: {TvRecipe.LayoutView, "app.html"}}, before_send: [#Function<0.101282891/1 in Plug.CSRFProtection.call/2>, #Function<4.111
648917/1 in Phoenix.Controller.fetch_flash/2>, #Function<0.61377594/1 in Plug.Session.before_send/2>, #Function<1.115972179/
1 in Plug.Logger.call/2>], body_params: %{}, cookies: %{}, halted: false, host: "www.example.com", method: "GET", owner: #PI
D<0.302.0>, params: %{}, path_info: ["sessions", "new"], path_params: %{}, peer: {{127, 0, 0, 1}, 111317}, port: 80, private
-: %{TvRecipe.Router => {[], %{}}, :phoenix_action => :new, :phoenix_controller => TvRecipe.SessionController, :phoenix_endpo
+: %{TvRecipe.Router => {[], %{}}, :phoenix_action => :new, :phoenix_controller => TvRecipeWeb.SessionController, :phoenix_endpo
int => TvRecipe.Endpoint, :phoenix_flash => %{}, :phoenix_format => "html", :phoenix_layout => {TvRecipe.LayoutView, :app},
:phoenix_pipelines => [:browser], :phoenix_recycled => true, :phoenix_route => #Function<12.75217690/1 in TvRecipe.Router.ma
-tch_route/4>, :phoenix_router => TvRecipe.Router, :phoenix_template => "new.html", :phoenix_view => TvRecipe.SessionView, :p
+tch_route/4>, :phoenix_router => TvRecipe.Router, :phoenix_template => "new.html", :phoenix_view => TvRecipeWeb.SessionView, :p
lug_session => %{}, :plug_session_fetch => :done, :plug_skip_csrf_protection => true}, query_params: %{}, query_string: "",
remote_ip: {127, 0, 0, 1}, req_cookies: %{}, req_headers: [], request_path: "/sessions/new", resp_body: nil, resp_cookies: %
{}, resp_headers: [{"cache-control", "max-age=0, private, must-revalidate"}, {"x-request-id", "eedn739jkdct1hr8r3nod6nst95b2
qvu"}, {"x-frame-options", "SAMEORIGIN"}, {"x-xss-protection", "1; mode=block"}, {"x-content-type-options", "nosniff"}], sch
eme: :http, script_name: [], secret_key_base: "XfacEiZ/QVO87L4qirM0thXcedgcx5zYhLPAsmVPnL8AVu6qB/Et84yvJ6712aSn", state: :un
-set, status: nil}, view_module: TvRecipe.SessionView, view_template: "new.html"})
+set, status: nil}, view_module: TvRecipeWeb.SessionView, view_template: "new.html"})
(tv_recipe) web/templates/layout/app.html.eex:29: TvRecipe.LayoutView."app.html"/1
(phoenix) lib/phoenix/view.ex:335: Phoenix.View.render_to_iodata/3
(phoenix) lib/phoenix/controller.ex:642: Phoenix.Controller.do_render/4
- (tv_recipe) web/controllers/session_controller.ex:1: TvRecipe.SessionController.action/2
- (tv_recipe) web/controllers/session_controller.ex:1: TvRecipe.SessionController.phoenix_controller_pipeline/2
+ (tv_recipe) web/controllers/session_controller.ex:1: TvRecipeWeb.SessionController.action/2
+ (tv_recipe) web/controllers/session_controller.ex:1: TvRecipeWeb.SessionController.phoenix_controller_pipeline/2
(tv_recipe) lib/tv_recipe/endpoint.ex:1: TvRecipe.Endpoint.instrument/4
(tv_recipe) lib/phoenix/router.ex:261: TvRecipe.Router.dispatch/2
- (tv_recipe) web/router.ex:1: TvRecipe.Router.do_call/2
+ (tv_recipe) lib/tv_recipe_web/router.ex:1: TvRecipe.Router.do_call/2
(tv_recipe) lib/tv_recipe/endpoint.ex:1: TvRecipe.Endpoint.phoenix_pipeline/1
(tv_recipe) lib/tv_recipe/endpoint.ex:1: TvRecipe.Endpoint.call/2
(phoenix) lib/phoenix/test/conn_test.ex:224: Phoenix.ConnTest.dispatch/5
- test/controllers/session_controller_test.exs:5: (test)
+ test/tv_recipe_web/controllers/session_controller_test.exs:5: (test)
Finished in 0.1 seconds
1 test, 1 failure
```
-测试失败,因为 `TvRecipe.SessionView` 未定义。
+测试失败,因为 `TvRecipeWeb.SessionView` 未定义。
## 创建 `SessionView` 模块
在 `web/views` 目录下新建一个 `session_view.ex` 文件,内容如下:
```elixir
-defmodule TvRecipe.SessionView do
- use TvRecipe.Web, :view
+defmodule TvRecipeWeb.SessionView do
+ use TvRecipeWeb, :view
end
```
在 Phoenix 下,View 与 templates 是分开的,其中 View 是模块(module),而 templates 在编译后,会变成 View 模块中的函数。这也是为什么我们在定义模板之前,要先定义视图的原因。
@@ -205,14 +205,14 @@ end
此时运行测试:
```bash
-mix test test/controllers/session_controller_test.exs
+mix test test/tv_recipe_web/controllers/session_controller_test.exs
Compiling 1 file (.ex)
Generated tv_recipe app
- 1) test renders form for new sessions (TvRecipe.SessionControllerTest)
- test/controllers/session_controller_test.exs:4
- ** (Phoenix.Template.UndefinedError) Could not render "new.html" for TvRecipe.SessionView, please define a matching cla
+ 1) test renders form for new sessions (TvRecipeWeb.SessionControllerTest)
+ test/tv_recipe_web/controllers/session_controller_test.exs:4
+ ** (Phoenix.Template.UndefinedError) Could not render "new.html" for TvRecipeWeb.SessionView, please define a matching cla
use for render/2 or define a template at "web/templates/session". No templates were compiled for this module.
Assigns:
@@ -221,31 +221,31 @@ ore_send: [#Function<0.101282891/1 in Plug.CSRFProtection.call/2>, #Function<4.1
/2>, #Function<0.61377594/1 in Plug.Session.before_send/2>, #Function<1.115972179/1 in Plug.Logger.call/2>], body_params: %{
}, cookies: %{}, halted: false, host: "www.example.com", method: "GET", owner: #PID<0.300.0>, params: %{}, path_info: ["sess
ions", "new"], path_params: %{}, peer: {{127, 0, 0, 1}, 111317}, port: 80, private: %{TvRecipe.Router => {[], %{}}, :phoenix
-_action => :new, :phoenix_controller => TvRecipe.SessionController, :phoenix_endpoint => TvRecipe.Endpoint, :phoenix_flash =
+_action => :new, :phoenix_controller => TvRecipeWeb.SessionController, :phoenix_endpoint => TvRecipe.Endpoint, :phoenix_flash =
> %{}, :phoenix_format => "html", :phoenix_layout => {TvRecipe.LayoutView, :app}, :phoenix_pipelines => [:browser], :phoenix
_recycled => true, :phoenix_route => #Function<12.75217690/1 in TvRecipe.Router.match_route/4>, :phoenix_router => TvRecipe.
-Router, :phoenix_template => "new.html", :phoenix_view => TvRecipe.SessionView, :plug_session => %{}, :plug_session_fetch =>
+Router, :phoenix_template => "new.html", :phoenix_view => TvRecipeWeb.SessionView, :plug_session => %{}, :plug_session_fetch =>
:done, :plug_skip_csrf_protection => true}, query_params: %{}, query_string: "", remote_ip: {127, 0, 0, 1}, req_cookies: %{
}, req_headers: [], request_path: "/sessions/new", resp_body: nil, resp_cookies: %{}, resp_headers: [{"cache-control", "max-
age=0, private, must-revalidate"}, {"x-request-id", "vi7asqkbb9153m6ku8btf8r50p38rsqn"}, {"x-frame-options", "SAMEORIGIN"},
{"x-xss-protection", "1; mode=block"}, {"x-content-type-options", "nosniff"}], scheme: :http, script_name: [], secret_key_ba
se: "XfacEiZ/QVO87L4qirM0thXcedgcx5zYhLPAsmVPnL8AVu6qB/Et84yvJ6712aSn", state: :unset, status: nil}, template_not_found: TvR
-ecipe.SessionView, view_module: TvRecipe.SessionView, view_template: "new.html"}
+ecipe.SessionView, view_module: TvRecipeWeb.SessionView, view_template: "new.html"}
stacktrace:
(phoenix) lib/phoenix/template.ex:364: Phoenix.Template.raise_template_not_found/3
(tv_recipe) web/templates/layout/app.html.eex:29: TvRecipe.LayoutView."app.html"/1
(phoenix) lib/phoenix/view.ex:335: Phoenix.View.render_to_iodata/3
(phoenix) lib/phoenix/controller.ex:642: Phoenix.Controller.do_render/4
- (tv_recipe) web/controllers/session_controller.ex:1: TvRecipe.SessionController.action/2
- (tv_recipe) web/controllers/session_controller.ex:1: TvRecipe.SessionController.phoenix_controller_pipeline/2
+ (tv_recipe) web/controllers/session_controller.ex:1: TvRecipeWeb.SessionController.action/2
+ (tv_recipe) web/controllers/session_controller.ex:1: TvRecipeWeb.SessionController.phoenix_controller_pipeline/2
(tv_recipe) lib/tv_recipe/endpoint.ex:1: TvRecipe.Endpoint.instrument/4
(tv_recipe) lib/phoenix/router.ex:261: TvRecipe.Router.dispatch/2
- (tv_recipe) web/router.ex:1: TvRecipe.Router.do_call/2
+ (tv_recipe) lib/tv_recipe_web/router.ex:1: TvRecipe.Router.do_call/2
(tv_recipe) lib/tv_recipe/endpoint.ex:1: TvRecipe.Endpoint.phoenix_pipeline/1
(tv_recipe) lib/tv_recipe/endpoint.ex:1: TvRecipe.Endpoint.call/2
(phoenix) lib/phoenix/test/conn_test.ex:224: Phoenix.ConnTest.dispatch/5
- test/controllers/session_controller_test.exs:5: (test)
+ test/tv_recipe_web/controllers/session_controller_test.exs:5: (test)
@@ -261,12 +261,12 @@ Finished in 0.1 seconds
现在运行测试:
```bash
-mix test test/controllers/session_controller_test.exs
+mix test test/tv_recipe_web/controllers/session_controller_test.exs
Compiling 1 file (.ex)
- 1) test renders form for new sessions (TvRecipe.SessionControllerTest)
- test/controllers/session_controller_test.exs:4
+ 1) test renders form for new sessions (TvRecipeWeb.SessionControllerTest)
+ test/tv_recipe_web/controllers/session_controller_test.exs:4
Assertion with =~ failed
code: html_response(conn, 200) =~ "登录"
left: "\n\n
\n ">
<%= label f, :email, class: "control-label" %>
<%= text_input f, :email, class: "form-control" %>
```
-`session_path(@conn, :create)` 是表单数据要提交的路径,`as: :session` 则表示表单数据提交时,是保存在 `session` 的键名下的。
+`Routes.session_path(@conn, :create)` 是表单数据要提交的路径,`as: :session` 则表示表单数据提交时,是保存在 `session` 的键名下的。
现在运行测试:
```bash
-mix test test/controllers/session_controller_test.exs
+mix test test/tv_recipe_web/controllers/session_controller_test.exs
Compiling 10 files (.ex)
- 1) test renders form for new sessions (TvRecipe.SessionControllerTest)
- test/controllers/session_controller_test.exs:4
+ 1) test renders form for new sessions (TvRecipeWeb.SessionControllerTest)
+ test/tv_recipe_web/controllers/session_controller_test.exs:4
** (ArgumentError) No helper clause for TvRecipe.Router.Helpers.session_path/2 defined for action :create.
The following session_path actions are defined under your router:
* :new
stacktrace:
(phoenix) lib/phoenix/router/helpers.ex:269: Phoenix.Router.Helpers.raise_route_error/5
- (tv_recipe) web/templates/session/new.html.eex:2: TvRecipe.SessionView."new.html"/1
+ (tv_recipe) web/templates/session/new.html.eex:2: TvRecipeWeb.SessionView."new.html"/1
(tv_recipe) web/templates/layout/app.html.eex:29: TvRecipe.LayoutView."app.html"/1
(phoenix) lib/phoenix/view.ex:335: Phoenix.View.render_to_iodata/3
(phoenix) lib/phoenix/controller.ex:642: Phoenix.Controller.do_render/4
- (tv_recipe) web/controllers/session_controller.ex:1: TvRecipe.SessionController.action/2
- (tv_recipe) web/controllers/session_controller.ex:1: TvRecipe.SessionController.phoenix_controller_pipeline/2
+ (tv_recipe) web/controllers/session_controller.ex:1: TvRecipeWeb.SessionController.action/2
+ (tv_recipe) web/controllers/session_controller.ex:1: TvRecipeWeb.SessionController.phoenix_controller_pipeline/2
(tv_recipe) lib/tv_recipe/endpoint.ex:1: TvRecipe.Endpoint.instrument/4
(tv_recipe) lib/phoenix/router.ex:261: TvRecipe.Router.dispatch/2
- (tv_recipe) web/router.ex:1: TvRecipe.Router.do_call/2
+ (tv_recipe) lib/tv_recipe_web/router.ex:1: TvRecipe.Router.do_call/2
(tv_recipe) lib/tv_recipe/endpoint.ex:1: TvRecipe.Endpoint.phoenix_pipeline/1
(tv_recipe) lib/tv_recipe/endpoint.ex:1: TvRecipe.Endpoint.call/2
(phoenix) lib/phoenix/test/conn_test.ex:224: Phoenix.ConnTest.dispatch/5
- test/controllers/session_controller_test.exs:5: (test)
+ test/tv_recipe_web/controllers/session_controller_test.exs:5: (test)
@@ -414,10 +414,10 @@ Finished in 0.07 seconds
我们需要在 `router.ex` 文件添加一个路由:
```elixir
-diff --git a/web/router.ex b/web/router.ex
+diff --git a/lib/tv_recipe_web/router.ex b/lib/tv_recipe_web/router.ex
index aac327c..e0406d2 100644
---- a/web/router.ex
-+++ b/web/router.ex
+--- a/lib/tv_recipe_web/router.ex
++++ b/lib/tv_recipe_web/router.ex
@@ -19,6 +19,7 @@ defmodule TvRecipe.Router do
get "/", PageController, :index
resources "/users", UserController
@@ -434,19 +434,20 @@ index aac327c..e0406d2 100644
如果我们此时在浏览器里访问 `/sessions/new` 页面,并提交用户登录数据,会怎样?不不不,不要在浏览器里尝试,我们用测试代码:
```elixir
-diff --git a/test/controllers/session_controller_test.exs b/test/controllers/session_controller_test.exs
+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/controllers/session_controller_test.exs
-+++ b/test/controllers/session_controller_test.exs
+--- 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 TvRecipe.SessionControllerTest do
+ defmodule TvRecipeWeb.SessionControllerTest do
use TvRecipe.ConnCase
-+ alias TvRecipe.{Repo, User}
++ 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, session_path(conn, :new)
+ conn = get conn, Routes.session_path(conn, :new)
# 200 响应,页面上带有“登录”
assert html_response(conn, 200) =~ "登录"
end
@@ -456,29 +457,29 @@ index 0372448..6835e40 100644
+ # 插入新用户
+ Repo.insert! user_changeset
+ # 用户登录
-+ conn = post conn, session_path(conn, :create), session: @valid_user_attrs
++ conn = post conn, Routes.session_path(conn, :create), session: @valid_user_attrs
+ # 显示“欢迎你”的消息
+ assert get_flash(conn, :info) == "欢迎你"
+ # 重定向到主页
-+ assert redirected_to(conn) == page_path(conn, :index)
++ assert redirected_to(conn) == Routes.page_path(conn, :index)
+ end
end
```
我们的测试结果是:
```bash
-$ mix test test/controllers/session_controller_test.exs
+$ mix test test/tv_recipe_web/controllers/session_controller_test.exs
Compiling 1 file (.ex)
warning: variable "user" is unused
- test/controllers/session_controller_test.exs:16
+ test/tv_recipe_web/controllers/session_controller_test.exs:16
.
- 1) test login user and redirect to home page when data is valid (TvRecipe.SessionControllerTest)
- test/controllers/session_controller_test.exs:13
- ** (UndefinedFunctionError) function TvRecipe.SessionController.create/2 is undefined or private
+ 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
```
-`TvRecipe.SessionController.create` 未定义。
+`TvRecipeWeb.SessionController.create` 未定义。
打开 `session_controller.ex` 文件,添加 `create` 动作:
@@ -488,9 +489,10 @@ index 66a5304..40ad02f 100644
--- a/web/controllers/session_controller.ex
+++ b/web/controllers/session_controller.ex
@@ -1,7 +1,20 @@
- defmodule TvRecipe.SessionController do
- use TvRecipe.Web, :controller
-+ alias TvRecipe.{Repo, User}
+ defmodule TvRecipeWeb.SessionController do
+ use TvRecipeWeb, :controller
++ alias TvRecipe.Repo
++ alias TvRecipe.Users.User
def new(conn, _params) do
render conn, "new.html"
@@ -504,7 +506,7 @@ index 66a5304..40ad02f 100644
+ user && Comeonin.Bcrypt.checkpw(password, user.password_hash) ->
+ conn
+ |> put_flash(:info, "欢迎你")
-+ |> redirect(to: page_path(conn, :index))
++ |> redirect(to: Routes.page_path(conn, :index))
+ end
+ end
end
@@ -520,7 +522,7 @@ index 66a5304..40ad02f 100644
现在运行测试:
```bash
-$ mix test test/controllers/session_controller_test.exs
+$ mix test test/tv_recipe_web/controllers/session_controller_test.exs
..
Finished in 0.2 seconds
@@ -536,13 +538,13 @@ Finished in 0.2 seconds
同样的,我们先写测试:
```elixir
-diff --git a/test/controllers/session_controller_test.exs b/test/controllers/session_controller_test.exs
+diff --git a/test/tv_recipe_web/controllers/session_controller_test.exs b/test/tv_recipe_web/controllers/session_controller_test.exs
index cc35f0a..dd5bc02 100644
---- a/test/controllers/session_controller_test.exs
-+++ b/test/controllers/session_controller_test.exs
-@@ -21,4 +21,24 @@ defmodule TvRecipe.SessionControllerTest do
+--- a/test/tv_recipe_web/controllers/session_controller_test.exs
++++ b/test/tv_recipe_web/controllers/session_controller_test.exs
+@@ -21,4 +21,24 @@ defmodule TvRecipeWeb.SessionControllerTest do
# 重定向到主页
- assert redirected_to(conn) == page_path(conn, :index)
+ assert redirected_to(conn) == Routes.page_path(conn, :index)
end
+
+ test "redirect to session new when email exists but with wrong password", %{conn: conn} do
@@ -550,7 +552,7 @@ index cc35f0a..dd5bc02 100644
+ # 插入新用户
+ Repo.insert! user_changeset
+ # 用户登录
-+ conn = post conn, session_path(conn, :create), session: %{@valid_user_attrs | password: ""}
++ conn = post conn, Routes.session_path(conn, :create), session: %{@valid_user_attrs | password: ""}
+ # 显示“用户名或密码错误”
+ assert get_flash(conn, :error) == "用户名或密码错误"
+ # 返回登录页
@@ -558,7 +560,7 @@ index cc35f0a..dd5bc02 100644
+ end
+
+ test "redirect to session new when nobody login", %{conn: conn} do
-+ conn = post conn, session_path(conn, :create), session: @valid_user_attrs
++ conn = post conn, Routes.session_path(conn, :create), session: @valid_user_attrs
+ # 显示“用户名或密码错误”
+ assert get_flash(conn, :error) == "用户名或密码错误"
+ # 返回登录页
@@ -573,10 +575,10 @@ diff --git a/web/controllers/session_controller.ex b/web/controllers/session_con
index 40ad02f..400a33c 100644
--- a/web/controllers/session_controller.ex
+++ b/web/controllers/session_controller.ex
-@@ -15,6 +15,18 @@ defmodule TvRecipe.SessionController do
+@@ -15,6 +15,18 @@ defmodule TvRecipeWeb.SessionController do
conn
|> put_flash(:info, "欢迎你")
- |> redirect(to: page_path(conn, :index))
+ |> redirect(to: Routes.page_path(conn, :index))
+ # 用户存在,但密码错误
+ user ->
+ conn
@@ -596,7 +598,7 @@ index 40ad02f..400a33c 100644
再次测试:
```bash
-mix test test/controllers/session_controller_test.exs
+mix test test/tv_recipe_web/controllers/session_controller_test.exs
....
Finished in 0.2 seconds
@@ -615,27 +617,27 @@ Finished in 0.2 seconds
我们来改造下我们的测试代码:
```elixir
-diff --git a/test/controllers/session_controller_test.exs b/test/controllers/session_controller_test.exs
+diff --git a/test/tv_recipe_web/controllers/session_controller_test.exs b/test/tv_recipe_web/controllers/session_controller_test.exs
index dd5bc02..52e8801 100644
---- a/test/controllers/session_controller_test.exs
-+++ b/test/controllers/session_controller_test.exs
-@@ -13,13 +13,19 @@ defmodule TvRecipe.SessionControllerTest do
+--- a/test/tv_recipe_web/controllers/session_controller_test.exs
++++ b/test/tv_recipe_web/controllers/session_controller_test.exs
+@@ -13,13 +13,19 @@ defmodule TvRecipeWeb.SessionControllerTest do
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
+ user = Repo.insert! user_changeset
# 用户登录
- conn = post conn, session_path(conn, :create), session: @valid_user_attrs
+ conn = post conn, Routes.session_path(conn, :create), session: @valid_user_attrs
# 显示“欢迎你”的消息
assert get_flash(conn, :info) == "欢迎你"
# 重定向到主页
- assert redirected_to(conn) == page_path(conn, :index)
+ assert redirected_to(conn) == Routes.page_path(conn, :index)
+ # 读取首页,页面上包含已登录用户的用户名
-+ conn = get conn, page_path(conn, :index)
++ conn = get conn, Routes.page_path(conn, :index)
+ assert html_response(conn, 200) =~ Map.get(@valid_user_attrs, :username)
+ # 读取用户页,页面上包含已登录用户的用户名
-+ conn = get conn, user_path(conn, :show, user)
++ conn = get conn, Routes.user_path(conn, :show, user)
+ assert html_response(conn, 200) =~ Map.get(@valid_user_attrs, :username)
end
```
@@ -660,7 +662,7 @@ index 82259d8..2d39904 100644
- Get Started
+ <%= if @current_user do %>
-+ - <%= link @current_user.username, to: user_path(@conn, :show, @current_user) %>
++ - <%= link @current_user.username, to: Routes.user_path(@conn, :show, @current_user) %>
+ <% end %>
@@ -733,7 +735,7 @@ index 0000000..84b17f7
--- /dev/null
+++ b/web/controllers/auth.ex
@@ -0,0 +1,16 @@
-+defmodule TvRecipe.Auth do
++defmodule TvRecipeWeb.Auth do
+ import Plug.Conn
+
+ @doc """
@@ -763,7 +765,7 @@ index 0000000..84b17f7
user && Comeonin.Bcrypt.checkpw(password, user.password_hash) ->
conn
|> put_flash(:info, "欢迎你")
- |> redirect(to: page_path(conn, :index))
+ |> redirect(to: Routes.page_path(conn, :index))
```
用户登录时,我们根据他们提供的邮箱取得数据库中的用户,然后比对密码,如果密码正确,我们就得到了 `user`。
@@ -779,13 +781,13 @@ diff --git a/web/controllers/session_controller.ex b/web/controllers/session_con
index 400a33c..b5218f2 100644
--- a/web/controllers/session_controller.ex
+++ b/web/controllers/session_controller.ex
-@@ -13,6 +13,7 @@ defmodule TvRecipe.SessionController do
+@@ -13,6 +13,7 @@ defmodule TvRecipeWeb.SessionController do
# 用户存在,且密码正确
user && Comeonin.Bcrypt.checkpw(password, user.password_hash) ->
conn
+ |> put_session(:user_id, user.id)
|> put_flash(:info, "欢迎你")
- |> redirect(to: page_path(conn, :index))
+ |> redirect(to: Routes.page_path(conn, :index))
```
然后我们就能在 `auth.ex` 文件中读取 session 中的 `:user_id` 了:
@@ -799,22 +801,22 @@ index 84b17f7..994112d 100644
def call(conn, repo) do
+ user_id = get_session(conn, :user_id)
-+ user = user_id && repo.get(TvRecipe.User, user_id)
++ user = user_id && repo.get(TvRecipe.Users.User, user_id)
assign(conn, :current_user, user)
end
```
最后,将 `Auth` plug 加入 `:browser` pipeline 中:
```elixir
-diff --git a/web/router.ex b/web/router.ex
+diff --git a/lib/tv_recipe_web/router.ex b/lib/tv_recipe_web/router.ex
index e0406d2..1265c86 100644
---- a/web/router.ex
-+++ b/web/router.ex
-@@ -7,6 +7,7 @@ defmodule TvRecipe.Router do
+--- 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 TvRecipe.Auth, repo: TvRecipe.Repo
++ plug TvRecipeWeb.Auth, repo: TvRecipe.Repo
end
pipeline :api do
diff --git a/05-session/02-auto-login-user.md b/05-session/02-auto-login-user.md
index 6444ad0..c0c1cdd 100644
--- a/05-session/02-auto-login-user.md
+++ b/05-session/02-auto-login-user.md
@@ -12,7 +12,7 @@
{:ok, _user} ->
conn
|> put_flash(:info, "User created successfully.")
- |> redirect(to: user_path(conn, :index))
+ |> redirect(to: Routes.user_path(conn, :index))
{:error, changeset} ->
render(conn, "new.html", changeset: changeset)
end
@@ -32,12 +32,12 @@ index 95d3108..26055e3 100644
@@ -17,8 +17,11 @@ defmodule TvRecipe.UserControllerTest do
test "creates resource and redirects when data is valid", %{conn: conn} do
- conn = post conn, user_path(conn, :create), user: @valid_attrs
-- assert redirected_to(conn) == user_path(conn, :index)
-+ assert redirected_to(conn) == page_path(conn, :index)
+ conn = post conn, Routes.user_path(conn, :create), user: @valid_attrs
+- assert redirected_to(conn) == Routes.user_path(conn, :index)
++ assert redirected_to(conn) == Routes.page_path(conn, :index)
assert Repo.get_by(User, @valid_attrs |> Map.delete(:password))
+ # 注册后自动登录,检查首页是否包含用户名
-+ conn = get conn, page_path(conn, :index)
++ conn = get conn, Routes.page_path(conn, :index)
+ assert html_response(conn, 200) =~ Map.get(@valid_attrs, :username)
end
```
@@ -56,9 +56,9 @@ index 7d13c5f..8d8a6f5 100644
+ {:ok, user} ->
conn
|> put_flash(:info, "User created successfully.")
-- |> redirect(to: user_path(conn, :index))
+- |> redirect(to: Routes.user_path(conn, :index))
+ |> put_session(:user_id, user.id)
-+ |> redirect(to: page_path(conn, :index))
++ |> redirect(to: Routes.page_path(conn, :index))
{:error, changeset} ->
render(conn, "new.html", changeset: changeset)
end
diff --git a/05-session/03-logout.md b/05-session/03-logout.md
index 6c86653..afbaa5c 100644
--- a/05-session/03-logout.md
+++ b/05-session/03-logout.md
@@ -11,7 +11,7 @@ index c5f4616..511d0ab 100644
+++ b/test/controllers/session_controller_test.exs
@@ -26,6 +26,8 @@ defmodule TvRecipe.SessionControllerTest do
# 读取用户页,页面上包含已登录用户的用户名
- conn = get conn, user_path(conn, :show, user)
+ conn = get conn, Routes.user_path(conn, :show, user)
assert html_response(conn, 200) =~ Map.get(@valid_user_attrs, :username)
+ # 登录后的页面显示“退出”
+ assert html_response(conn, 200) =~ "退出"
@@ -52,7 +52,7 @@ index 2d39904..6c87a08 100644
Get Started
<%= if @current_user do %>
<%= link @current_user.username, to: user_path(@conn, :show, @current_user) %>
-+
<%= link "退出", to: session_path(@conn, :delete, @current_user), method: "delete" %>
++
<%= link "退出", to: Routes.session_path(@conn, :delete, @current_user), method: "delete" %>
<% end %>
@@ -135,12 +135,12 @@ index 511d0ab..969662a 100644
+ user = Repo.insert!(changeset)
+
+ # 登录该用户
-+ conn = post conn, session_path(conn, :create), session: Map.delete(@valid_user_attrs, :username)
++ conn = post conn, Routes.session_path(conn, :create), session: Map.delete(@valid_user_attrs, :username)
+
+ # 点击退出
-+ conn = delete conn, session_path(conn, :delete, user)
++ conn = delete conn, Routes.session_path(conn, :delete, user)
+ assert get_flash(conn, :info) == "退出成功"
-+ assert redirected_to(conn) == page_path(conn, :index)
++ assert redirected_to(conn) == Routes.page_path(conn, :index)
+ end
+
end
@@ -161,7 +161,7 @@ index b5218f2..2a887ee 100644
+ conn
+ |> delete_session(:user_id)
+ |> put_flash(:info, "退出成功")
-+ |> redirect(to: page_path(conn, :index))
++ |> redirect(to: Routes.page_path(conn, :index))
+ end
end
diff --git a/05-session/04-login-logout-buttons.md b/05-session/04-login-logout-buttons.md
index 31de2ba..18e6082 100644
--- a/05-session/04-login-logout-buttons.md
+++ b/05-session/04-login-logout-buttons.md
@@ -14,11 +14,11 @@ index 969662a..98fbb5a 100644
# 插入新用户
user = Repo.insert! user_changeset
+ # 未登录情况下访问首页,应带有登录/注册文字
-+ conn = get conn, page_path(conn, :index)
++ conn = get conn, Routes.page_path(conn, :index)
+ assert html_response(conn, 200) =~ "登录"
+ assert html_response(conn, 200) =~ "注册"
# 用户登录
- conn = post conn, session_path(conn, :create), session: @valid_user_attrs
+ conn = post conn, Routes.session_path(conn, :create), session: @valid_user_attrs
# 显示“欢迎你”的消息
```
@@ -31,11 +31,11 @@ index 6c87a08..b13f370 100644
+++ b/web/templates/layout/app.html.eex
@@ -20,6 +20,9 @@
<%= if @current_user do %>
-
<%= link @current_user.username, to: user_path(@conn, :show, @current_user) %>
-
<%= link "退出", to: session_path(@conn, :delete, @current_user), method: "delete" %>
+
<%= link @current_user.username, to: Routes.user_path(@conn, :show, @current_user) %>
+
<%= link "退出", to: Routes.session_path(@conn, :delete, @current_user), method: "delete" %>
+ <% else %>
-+
<%= link "登录", to: session_path(@conn, :new) %>
-+
<%= link "注册", to: user_path(@conn, :new) %>
++
<%= link "登录", to: Routes.session_path(@conn, :new) %>
++
<%= link "注册", to: Routes.user_path(@conn, :new) %>
<% end %>
@@ -63,7 +63,7 @@ index 6ac524c..0c2eb0a 100644
|> put_session(:user_id, user.id)
|> put_flash(:info, "欢迎你")
+ |> configure_session(renew: true)
- |> redirect(to: page_path(conn, :index))
+ |> redirect(to: Routes.page_path(conn, :index))
# 用户存在,但密码错误
user ->
diff --git a/web/controllers/user_controller.ex b/web/controllers/user_controller.ex
@@ -75,7 +75,7 @@ index 8d8a6f5..8b9b38b 100644
|> put_flash(:info, "User created successfully.")
|> put_session(:user_id, user.id)
+ |> configure_session(renew: true)
- |> redirect(to: page_path(conn, :index))
+ |> redirect(to: Routes.page_path(conn, :index))
{:error, changeset} ->
render(conn, "new.html", changeset: changeset)
```
@@ -88,7 +88,7 @@ diff --git a/web/controllers/auth.ex b/web/controllers/auth.ex
index 994112d..e298b68 100644
--- a/web/controllers/auth.ex
+++ b/web/controllers/auth.ex
-@@ -15,4 +15,10 @@ defmodule TvRecipe.Auth do
+@@ -15,4 +15,10 @@ defmodule TvRecipeWeb.Auth do
assign(conn, :current_user, user)
end
@@ -111,8 +111,8 @@ index 0c2eb0a..6f29ce0 100644
- |> put_session(:user_id, user.id)
|> put_flash(:info, "欢迎你")
- |> configure_session(renew: true)
-+ |> TvRecipe.Auth.login(user)
- |> redirect(to: page_path(conn, :index))
++ |> TvRecipeWeb.Auth.login(user)
+ |> redirect(to: Routes.page_path(conn, :index))
# 用户存在,但密码错误
user ->
diff --git a/web/controllers/user_controller.ex b/web/controllers/user_controller.ex
@@ -125,8 +125,8 @@ index 8b9b38b..b9234b1 100644
|> put_flash(:info, "User created successfully.")
- |> put_session(:user_id, user.id)
- |> configure_session(renew: true)
-+ |> TvRecipe.Auth.login(user)
- |> redirect(to: page_path(conn, :index))
++ |> TvRecipeWeb.Auth.login(user)
+ |> redirect(to: Routes.page_path(conn, :index))
{:error, changeset} ->
render(conn, "new.html", changeset: changeset)
```
diff --git a/06-restrict-access/06-restrict-access.md b/06-restrict-access/06-restrict-access.md
index 5b363e4..5af01b3 100644
--- a/06-restrict-access/06-restrict-access.md
+++ b/06-restrict-access/06-restrict-access.md
@@ -11,21 +11,21 @@ diff --git a/test/controllers/user_controller_test.exs b/test/controllers/user_c
index 26055e3..ac6894e 100644
--- a/test/controllers/user_controller_test.exs
+++ b/test/controllers/user_controller_test.exs
-@@ -66,4 +66,18 @@ defmodule TvRecipe.UserControllerTest do
- assert redirected_to(conn) == user_path(conn, :index)
+@@ -66,4 +66,18 @@ defmodule TvRecipeWeb.UserControllerTest do
+ assert redirected_to(conn) == Routes.user_path(conn, :index)
refute Repo.get(User, user.id)
end
+
+ test "guest access user action redirected to login page", %{conn: conn} do
+ user = Repo.insert! %User{}
+ Enum.each([
-+ get(conn, user_path(conn, :index)),
-+ get(conn, user_path(conn, :show, user)),
-+ get(conn, user_path(conn, :edit, user)),
-+ put(conn, user_path(conn, :update, user), user: %{}),
-+ delete(conn, user_path(conn, :delete, user))
++ get(conn, Routes.user_path(conn, :index)),
++ get(conn, Routes.user_path(conn, :show, user)),
++ get(conn, Routes.user_path(conn, :edit, user)),
++ put(conn, Routes.user_path(conn, :update, user), user: %{}),
++ delete(conn, Routes.user_path(conn, :delete, user))
+ ], fn conn ->
-+ assert redirected_to(conn) == session_path(conn, :new)
++ assert redirected_to(conn) == Routes.session_path(conn, :new)
+ assert conn.halted
+ end)
+ end
@@ -39,15 +39,15 @@ index b9234b1..7bb7dac 100644
--- a/web/controllers/user_controller.ex
+++ b/web/controllers/user_controller.ex
@@ -1,5 +1,6 @@
- defmodule TvRecipe.UserController do
- use TvRecipe.Web, :controller
+ defmodule TvRecipeWeb.UserController do
+ use TvRecipeWeb, :controller
+ plug :login_require when action in [:index, :show, :edit, :update, :delete]
alias TvRecipe.User
-@@ -63,4 +64,20 @@ defmodule TvRecipe.UserController do
+@@ -63,4 +64,20 @@ defmodule TvRecipeWeb.UserController do
|> put_flash(:info, "User deleted successfully.")
- |> redirect(to: user_path(conn, :index))
+ |> redirect(to: Routes.user_path(conn, :index))
end
+
+ @doc """
@@ -61,7 +61,7 @@ index b9234b1..7bb7dac 100644
+ else
+ conn
+ |> put_flash(:info, "请先登录")
-+ |> redirect(to: session_path(conn, :new))
++ |> redirect(to: Routes.session_path(conn, :new))
+ |> halt()
+ end
+ end
@@ -99,7 +99,7 @@ conn
$ mix test
.....................
- 1) test renders form for editing chosen resource (TvRecipe.UserControllerTest)
+ 1) test renders form for editing chosen resource (TvRecipeWeb.UserControllerTest)
test/controllers/user_controller_test.exs:44
** (RuntimeError) expected response with status 200, got: 302, with body:
You are being
redirected.
@@ -110,7 +110,7 @@ $ mix test
- 2) test lists all entries on index (TvRecipe.UserControllerTest)
+ 2) test lists all entries on index (TvRecipeWeb.UserControllerTest)
test/controllers/user_controller_test.exs:8
** (RuntimeError) expected response with status 200, got: 302, with body:
You are being
redirected.
@@ -121,7 +121,7 @@ $ mix test
- 3) test renders page not found when id is nonexistent (TvRecipe.UserControllerTest)
+ 3) test renders page not found when id is nonexistent (TvRecipeWeb.UserControllerTest)
test/controllers/user_controller_test.exs:38
expected error to be sent as 404 status, but response sent 302 without error
stacktrace:
@@ -130,10 +130,10 @@ $ mix test
..
- 4) test deletes chosen resource (TvRecipe.UserControllerTest)
+ 4) test deletes chosen resource (TvRecipeWeb.UserControllerTest)
test/controllers/user_controller_test.exs:63
Assertion with == failed
- code: redirected_to(conn) == user_path(conn, :index)
+ code: redirected_to(conn) == Routes.user_path(conn, :index)
left: "/sessions/new"
right: "/users"
stacktrace:
@@ -141,7 +141,7 @@ $ mix test
- 5) test shows chosen resource (TvRecipe.UserControllerTest)
+ 5) test shows chosen resource (TvRecipeWeb.UserControllerTest)
test/controllers/user_controller_test.exs:32
** (RuntimeError) expected response with status 200, got: 302, with body:
You are being
redirected.
@@ -152,10 +152,10 @@ $ mix test
- 6) test updates chosen resource and redirects when data is valid (TvRecipe.UserControllerTest)
+ 6) test updates chosen resource and redirects when data is valid (TvRecipeWeb.UserControllerTest)
test/controllers/user_controller_test.exs:50
Assertion with == failed
- code: redirected_to(conn) == user_path(conn, :show, user)
+ code: redirected_to(conn) == Routes.user_path(conn, :show, user)
left: "/sessions/new"
right: "/users/1121"
stacktrace:
@@ -163,7 +163,7 @@ $ mix test
- 7) test does not update chosen resource and renders errors when data is invalid (TvRecipe.UserControllerTest)
+ 7) test does not update chosen resource and renders errors when data is invalid (TvRecipeWeb.UserControllerTest)
test/controllers/user_controller_test.exs:57
** (RuntimeError) expected response with status 200, got: 302, with body:
You are being
redirected.
@@ -186,8 +186,8 @@ Finished in 0.4 seconds
```elixir
test "shows chosen resource", %{conn: conn} do
user = Repo.insert! User.changeset(%User{}, @valid_attrs)
- conn = post conn, session_path(conn, :create), session: @valid_attrs # <= 这一行,登录用户
- conn = get conn, user_path(conn, :show, user)
+ conn = post conn, Routes.session_path(conn, :create), session: @valid_attrs # <= 这一行,登录用户
+ conn = get conn, Routes.user_path(conn, :show, user)
assert html_response(conn, 200) =~ "Show user"
end
```
@@ -207,75 +207,38 @@ index ac6894e..e11df40 100644
--- a/test/controllers/user_controller_test.exs
+++ b/test/controllers/user_controller_test.exs
@@ -1,10 +1,22 @@
- defmodule TvRecipe.UserControllerTest do
+ defmodule TvRecipeWeb.UserControllerTest do
use TvRecipe.ConnCase
-- alias TvRecipe.User
-+ alias TvRecipe.{Repo, User}
+ alias TvRecipe.Repo
+ alias TvRecipe.Users
+ alias TvRecipe.Users.User
@valid_attrs %{email: "chenxsan@gmail.com", password: "some content", username: "chenxsan"}
@invalid_attrs %{}
-+ setup %{conn: conn} = context do
-+ if context[:logged_in] == true do
-+ # 如果上下文里 :logged_in 值为 true
-+ user = Repo.insert! User.changeset(%User{}, @valid_attrs)
-+ conn = post conn, session_path(conn, :create), session: @valid_attrs
-+ {:ok, [conn: conn, user: user]}
-+ else
-+ :ok
-+ end
-+ end
-+
-+ @tag logged_in: true
- test "lists all entries on index", %{conn: conn} do
- conn = get conn, user_path(conn, :index)
- assert html_response(conn, 200) =~ "Listing users"
-@@ -29,24 +41,28 @@ defmodule TvRecipe.UserControllerTest do
- assert html_response(conn, 200) =~ "New user"
- end
-
-+ @tag logged_in: true
- test "shows chosen resource", %{conn: conn} do
- user = Repo.insert! %User{}
- conn = get conn, user_path(conn, :show, user)
- assert html_response(conn, 200) =~ "Show user"
- end
-
-+ @tag logged_in: true
- test "renders page not found when id is nonexistent", %{conn: conn} do
- assert_error_sent 404, fn ->
- get conn, user_path(conn, :show, -1)
- end
- end
+- defp create_user(_) do
+- user = fixture(:user)
+- %{user: user}
+- end
-+ @tag logged_in: true
- test "renders form for editing chosen resource", %{conn: conn} do
- user = Repo.insert! %User{}
- conn = get conn, user_path(conn, :edit, user)
- assert html_response(conn, 200) =~ "Edit user"
- end
++ defp login_user(%{conn: conn}) do
++ user = fixture(:user)
++ conn = post conn, Routes.session_path(conn, :create), session: @valid_attrs
++ %{conn: conn, user: user}
++ end
-+ @tag logged_in: true
- test "updates chosen resource and redirects when data is valid", %{conn: conn} do
- user = Repo.insert! %User{}
- conn = put conn, user_path(conn, :update, user), user: @valid_attrs
-@@ -54,12 +70,14 @@ defmodule TvRecipe.UserControllerTest do
- assert Repo.get_by(User, @valid_attrs |> Map.delete(:password))
- end
+ describe "edit user" do
+- setup [:create_user]
++ setup [:login_user]
-+ @tag logged_in: true
- test "does not update chosen resource and renders errors when data is invalid", %{conn: conn} do
- user = Repo.insert! %User{}
- conn = put conn, user_path(conn, :update, user), user: @invalid_attrs
- assert html_response(conn, 200) =~ "Edit user"
+ test "renders form for editing chosen user", %{conn: conn, user: user} do
+ conn = get(conn, Routes.user_path(conn, :edit, user))
+ assert html_response(conn, 200) =~ "Edit User"
end
+ end
-+ @tag logged_in: true
- test "deletes chosen resource", %{conn: conn} do
- user = Repo.insert! %User{}
- conn = delete conn, user_path(conn, :delete, user)
```
-我们根据 `logged_in` 的值返回不同 `conn`:一个是用户登录的 conn,一个是未登录的 conn。
+我们根据 `describe` 设置不同的 `setup`:需要用户登录则添加 `setup [:login_user]`, 不需要则不添加或留空 `setup []`
现在运行测试:
@@ -283,7 +246,7 @@ index ac6894e..e11df40 100644
$ mix test
...........................
- 1) test updates chosen resource and redirects when data is valid (TvRecipe.UserControllerTest)
+ 1) test updates chosen resource and redirects when data is valid (TvRecipeWeb.UserControllerTest)
test/controllers/user_controller_test.exs:66
** (RuntimeError) expected redirection with status 302, got: 200
stacktrace:
@@ -306,13 +269,12 @@ diff --git a/test/controllers/user_controller_test.exs b/test/controllers/user_c
index e11df40..c8263c6 100644
--- a/test/controllers/user_controller_test.exs
+++ b/test/controllers/user_controller_test.exs
-@@ -65,7 +65,7 @@ defmodule TvRecipe.UserControllerTest do
- @tag logged_in: true
+@@ -65,7 +65,7 @@ defmodule TvRecipeWeb.UserControllerTest do
test "updates chosen resource and redirects when data is valid", %{conn: conn} do
user = Repo.insert! %User{}
-- conn = put conn, user_path(conn, :update, user), user: @valid_attrs
-+ conn = put conn, user_path(conn, :update, user), user: %{@valid_attrs | username: "samchen", email: "chenxsan+1@gmail.com"}
- assert redirected_to(conn) == user_path(conn, :show, user)
+- conn = put conn, Routes.user_path(conn, :update, user), user: @valid_attrs
++ conn = put conn, Routes.user_path(conn, :update, user), user: %{@valid_attrs | username: "samchen", email: "chenxsan+1@gmail.com"}
+ assert redirected_to(conn) == Routes.user_path(conn, :show, user)
assert Repo.get_by(User, @valid_attrs |> Map.delete(:password))
end
```
@@ -331,8 +293,8 @@ index 7bb7dac..c0056fd 100644
--- a/web/controllers/user_controller.ex
+++ b/web/controllers/user_controller.ex
@@ -1,14 +1,9 @@
- defmodule TvRecipe.UserController do
- use TvRecipe.Web, :controller
+ defmodule TvRecipeWeb.UserController do
+ use TvRecipeWeb, :controller
- plug :login_require when action in [:index, :show, :edit, :update, :delete]
+ plug :login_require when action in [:show, :edit, :update]
@@ -346,7 +308,7 @@ index 7bb7dac..c0056fd 100644
def new(conn, _params) do
changeset = User.changeset(%User{})
render(conn, "new.html", changeset: changeset)
-@@ -53,18 +48,6 @@ defmodule TvRecipe.UserController do
+@@ -53,18 +48,6 @@ defmodule TvRecipeWeb.UserController do
end
end
@@ -359,7 +321,7 @@ index 7bb7dac..c0056fd 100644
-
- conn
- |> put_flash(:info, "User deleted successfully.")
-- |> redirect(to: user_path(conn, :index))
+- |> redirect(to: Routes.user_path(conn, :index))
- end
-
@doc """
@@ -372,52 +334,49 @@ diff --git a/test/controllers/user_controller_test.exs b/test/controllers/user_c
index c8263c6..a2ccee0 100644
--- a/test/controllers/user_controller_test.exs
+++ b/test/controllers/user_controller_test.exs
-@@ -16,12 +16,6 @@ defmodule TvRecipe.UserControllerTest do
+@@ -16,12 +16,6 @@ defmodule TvRecipeWeb.UserControllerTest do
end
end
-- @tag logged_in: true
- test "lists all entries on index", %{conn: conn} do
-- conn = get conn, user_path(conn, :index)
+- conn = get conn, Routes.user_path(conn, :index)
- assert html_response(conn, 200) =~ "Listing users"
- end
end
end
-- @tag logged_in: true
- test "lists all entries on index", %{conn: conn} do
-- conn = get conn, user_path(conn, :index)
+- conn = get conn, Routes.user_path(conn, :index)
- assert html_response(conn, 200) =~ "Listing users"
- end
-
test "renders form for new resources", %{conn: conn} do
- conn = get conn, user_path(conn, :new)
+ conn = get conn, Routes.user_path(conn, :new)
assert html_response(conn, 200) =~ "New user"
-@@ -77,22 +71,12 @@ defmodule TvRecipe.UserControllerTest do
+@@ -77,22 +71,12 @@ defmodule TvRecipeWeb.UserControllerTest do
assert html_response(conn, 200) =~ "Edit user"
end
-- @tag logged_in: true
- test "deletes chosen resource", %{conn: conn} do
- user = Repo.insert! %User{}
-- conn = delete conn, user_path(conn, :delete, user)
-- assert redirected_to(conn) == user_path(conn, :index)
+- conn = delete conn, Routes.user_path(conn, :delete, user)
+- assert redirected_to(conn) == Routes.user_path(conn, :index)
- refute Repo.get(User, user.id)
- end
-
test "guest access user action redirected to login page", %{conn: conn} do
user = Repo.insert! %User{}
Enum.each([
-- get(conn, user_path(conn, :index)),
- get(conn, user_path(conn, :show, user)),
- get(conn, user_path(conn, :edit, user)),
- put(conn, user_path(conn, :update, user), user: %{}),
-- delete(conn, user_path(conn, :delete, user))
+- get(conn, Routes.user_path(conn, :index)),
+ get(conn, Routes.user_path(conn, :show, user)),
+ get(conn, Routes.user_path(conn, :edit, user)),
+ put(conn, Routes.user_path(conn, :update, user), user: %{}),
+- delete(conn, Routes.user_path(conn, :delete, user))
], fn conn ->
- assert redirected_to(conn) == session_path(conn, :new)
+ assert redirected_to(conn) == Routes.session_path(conn, :new)
assert conn.halted
], fn conn ->
- assert redirected_to(conn) == session_path(conn, :new)
+ assert redirected_to(conn) == Routes.session_path(conn, :new)
assert conn.halted
diff --git a/web/templates/user/edit.html.eex b/web/templates/user/edit.html.eex
index 7e08f2b..beae173 100644
@@ -472,7 +431,7 @@ diff --git a/test/controllers/user_controller_test.exs b/test/controllers/user_c
index a2ccee0..fd57531 100644
--- a/test/controllers/user_controller_test.exs
+++ b/test/controllers/user_controller_test.exs
-@@ -82,4 +82,19 @@ defmodule TvRecipe.UserControllerTest do
+@@ -82,4 +82,19 @@ defmodule TvRecipeWeb.UserControllerTest do
assert conn.halted
end)
end
@@ -481,12 +440,12 @@ index a2ccee0..fd57531 100644
+ test "does not allow access to other user path", %{conn: conn, user: user} do
+ another_user = Repo.insert! %User{}
+ Enum.each([
-+ get(conn, user_path(conn, :show, another_user)),
-+ get(conn, user_path(conn, :edit, another_user)),
-+ put(conn, user_path(conn, :update, another_user), user: %{})
++ get(conn, Routes.user_path(conn, :show, another_user)),
++ get(conn, Routes.user_path(conn, :edit, another_user)),
++ put(conn, Routes.user_path(conn, :update, another_user), user: %{})
+ ], fn conn ->
+ assert get_flash(conn, :error) == "禁止访问未授权页面"
-+ assert redirected_to(conn) == user_path(conn, :show, user)
++ assert redirected_to(conn) == Routes.user_path(conn, :show, user)
+ assert conn.halted
+ end)
+ end
@@ -501,14 +460,14 @@ index c0056fd..520d986 100644
--- a/web/controllers/user_controller.ex
+++ b/web/controllers/user_controller.ex
@@ -1,6 +1,7 @@
- defmodule TvRecipe.UserController do
- use TvRecipe.Web, :controller
+ defmodule TvRecipeWeb.UserController do
+ use TvRecipeWeb, :controller
plug :login_require when action in [:show, :edit, :update]
+ plug :self_require when action in [:show, :edit, :update]
alias TvRecipe.User
-@@ -63,4 +64,21 @@ defmodule TvRecipe.UserController do
+@@ -63,4 +64,21 @@ defmodule TvRecipeWeb.UserController do
|> halt()
end
end
@@ -525,7 +484,7 @@ index c0056fd..520d986 100644
+ else
+ conn
+ |> put_flash(:error, "禁止访问未授权页面")
-+ |> redirect(to: user_path(conn, :show, conn.assigns.current_user))
++ |> redirect(to: Routes.user_path(conn, :show, conn.assigns.current_user))
+ |> halt()
+ end
+ end
@@ -539,7 +498,7 @@ index c0056fd..520d986 100644
$ mix test
....................
- 1) test shows chosen resource (TvRecipe.UserControllerTest)
+ 1) test shows chosen resource (TvRecipeWeb.UserControllerTest)
test/controllers/user_controller_test.exs:39
** (RuntimeError) expected response with status 200, got: 302, with body:
You are being
redirected.
@@ -550,7 +509,7 @@ $ mix test
- 2) test renders form for editing chosen resource (TvRecipe.UserControllerTest)
+ 2) test renders form for editing chosen resource (TvRecipeWeb.UserControllerTest)
test/controllers/user_controller_test.exs:53
** (RuntimeError) expected response with status 200, got: 302, with body:
You are being
redirected.
@@ -561,10 +520,10 @@ $ mix test
....
- 3) test updates chosen resource and redirects when data is valid (TvRecipe.UserControllerTest)
+ 3) test updates chosen resource and redirects when data is valid (TvRecipeWeb.UserControllerTest)
test/controllers/user_controller_test.exs:60
Assertion with == failed
- code: redirected_to(conn) == user_path(conn, :show, user)
+ code: redirected_to(conn) == Routes.user_path(conn, :show, user)
left: "/users/2948"
right: "/users/2949"
stacktrace:
@@ -572,7 +531,7 @@ $ mix test
- 4) test renders page not found when id is nonexistent (TvRecipe.UserControllerTest)
+ 4) test renders page not found when id is nonexistent (TvRecipeWeb.UserControllerTest)
test/controllers/user_controller_test.exs:46
expected error to be sent as 404 status, but response sent 302 without error
stacktrace:
@@ -581,7 +540,7 @@ $ mix test
.
- 5) test does not update chosen resource and renders errors when data is invalid (TvRecipe.UserControllerTest)
+ 5) test does not update chosen resource and renders errors when data is invalid (TvRecipeWeb.UserControllerTest)
test/controllers/user_controller_test.exs:68
** (RuntimeError) expected response with status 200, got: 302, with body:
You are being
redirected.
@@ -602,7 +561,7 @@ diff --git a/test/controllers/user_controller_test.exs b/test/controllers/user_c
index fd57531..a1b75c6 100644
--- a/test/controllers/user_controller_test.exs
+++ b/test/controllers/user_controller_test.exs
-@@ -3,6 +3,7 @@ defmodule TvRecipe.UserControllerTest do
+@@ -3,6 +3,7 @@ defmodule TvRecipeWeb.UserControllerTest do
alias TvRecipe.{Repo, User}
@valid_attrs %{email: "chenxsan@gmail.com", password: "some content", username: "chenxsan"}
@@ -610,52 +569,46 @@ index fd57531..a1b75c6 100644
@invalid_attrs %{}
setup %{conn: conn} = context do
-@@ -36,37 +37,26 @@ defmodule TvRecipe.UserControllerTest do
+@@ -36,37 +37,26 @@ defmodule TvRecipeWeb.UserControllerTest do
end
- @tag logged_in: true
- test "shows chosen resource", %{conn: conn} do
- user = Repo.insert! %User{}
+ test "shows chosen resource", %{conn: conn, user: user} do
- conn = get conn, user_path(conn, :show, user)
+ conn = get conn, Routes.user_path(conn, :show, user)
assert html_response(conn, 200) =~ "Show user"
end
- @tag logged_in: true
- test "renders page not found when id is nonexistent", %{conn: conn} do
- assert_error_sent 404, fn ->
-- get conn, user_path(conn, :show, -1)
- @tag logged_in: true
+- get conn, Routes.user_path(conn, :show, -1)
- test "renders page not found when id is nonexistent", %{conn: conn} do
- assert_error_sent 404, fn ->
-- get conn, user_path(conn, :show, -1)
+- get conn, Routes.user_path(conn, :show, -1)
- end
- end
-
-- @tag logged_in: true
- test "renders form for editing chosen resource", %{conn: conn} do
- user = Repo.insert! %User{}
+ test "renders form for editing chosen resource", %{conn: conn, user: user} do
- conn = get conn, user_path(conn, :edit, user)
+ conn = get conn, Routes.user_path(conn, :edit, user)
assert html_response(conn, 200) =~ "Edit user"
end
- @tag logged_in: true
- test "updates chosen resource and redirects when data is valid", %{conn: conn} do
- user = Repo.insert! %User{}
-- conn = put conn, user_path(conn, :update, user), user: %{@valid_attrs | username: "samchen", email: "chenxsan+1@gmail.com"}
+- conn = put conn, Routes.user_path(conn, :update, user), user: %{@valid_attrs | username: "samchen", email: "chenxsan+1@gmail.com"}
+ test "updates chosen resource and redirects when data is valid", %{conn: conn, user: user} do
-+ conn = put conn, user_path(conn, :update, user), user: @another_valid_attrs
- assert redirected_to(conn) == user_path(conn, :show, user)
++ conn = put conn, Routes.user_path(conn, :update, user), user: @another_valid_attrs
+ assert redirected_to(conn) == Routes.user_path(conn, :show, user)
- assert Repo.get_by(User, @valid_attrs |> Map.delete(:password))
+ assert Repo.get_by(User, @another_valid_attrs |> Map.delete(:password))
end
- @tag logged_in: true
- test "does not update chosen resource and renders errors when data is invalid", %{conn: conn} do
- user = Repo.insert! %User{}
+ test "does not update chosen resource and renders errors when data is invalid", %{conn: conn, user: user} do
- conn = put conn, user_path(conn, :update, user), user: @invalid_attrs
+ conn = put conn, Routes.user_path(conn, :update, user), user: @invalid_attrs
assert html_response(conn, 200) =~ "Edit user"
end
```
diff --git a/07-recipe/01-gen-html.md b/07-recipe/01-gen-html.md
index c87a8b7..a5116be 100644
--- a/07-recipe/01-gen-html.md
+++ b/07-recipe/01-gen-html.md
@@ -13,10 +13,10 @@ episode|integer|第几集|必填|1
content|text|内容|必填|
user_id|integer|关联用户 id|必填|
-这里我们可以直接使用 `mix phoenix.gen.html` 命令来生成菜谱相关的所有文件:
+这里我们可以直接使用 `mix phx.gen.html` 命令来生成菜谱相关的所有文件:
```bash
-$ mix phoenix.gen.html Recipe recipes name title season:integer episode:integer content:text user_id:references:users
+$ mix phx.gen.html Recipes Recipe recipes name title season:integer episode:integer content:text user_id:references:users
* creating web/controllers/recipe_controller.ex
* creating web/templates/recipe/edit.html.eex
* creating web/templates/recipe/form.html.eex
@@ -37,7 +37,7 @@ Remember to update your repository by running migrations:
$ mix ecto.migrate
```
-
+
我们先按照提示把 `resources "/recipes", RecipeController` 加入 `web/router.ex` 文件中:
@@ -46,7 +46,7 @@ diff --git a/web/router.ex b/web/router.ex
index e0811dc..a6d7cd5 100644
--- a/web/router.ex
+++ b/web/router.ex
-@@ -20,6 +20,7 @@ defmodule TvRecipe.Router do
+@@ -20,6 +20,7 @@ defmodule TvRecipeWeb.Router do
get "/", PageController, :index
resources "/users", UserController, except: [:index, :delete]
resources "/sessions", SessionController, only: [:new, :create, :delete]
@@ -56,21 +56,24 @@ index e0811dc..a6d7cd5 100644
但请不要着急执行 `mix ecto.migrate`,我们有几个需要调整的地方:
+注: 运行了的话可使用 `mix ecto.rollback` 回撤修改
+
1. 新建的 `priv/repo/migrations/20170206013306_create_recipe.exs` 文件中,有如下一句代码:
```elixir
add :user_id, references(:users, on_delete: :nothing)
```
`on_delete` 决定 `recipe` 关联的 `user` 被删时,我们要如何处置 `recipe`。`:nothing` 表示不动 `recipe`,`:delete_all` 表示悉数删除,这里我们使用 `:delete_all`。
-2. 新建的 `web/models/recipe.ex` 文件中,有一句代码:
+2. 新建的 `lib/tv_recipe/recipes/recipe.ex` 文件中,有一句代码要替换:
```elixir
- belongs_to :user, TvRecipe.User
+ -field :user_id, :id
+ +belongs_to :user, TvRecipe.Users.User
```
因为 `Recipe` 与 `User` 的关系是双向的,所以我们需要在 `user.ex` 文件中增加一句:
```elixir
- has_many :recipes, TvRecipe.Recipe
+ has_many :recipes, TvRecipe.Recipes.Recipe
```
3. 我们需要在 `recipe.ex` 文件中给 `season` 与 `episode` 设置默认值:
diff --git a/07-recipe/02-recipe-scheme.md b/07-recipe/02-recipe-scheme.md
index ce94dd5..57e605f 100644
--- a/07-recipe/02-recipe-scheme.md
+++ b/07-recipe/02-recipe-scheme.md
@@ -2,7 +2,7 @@
在开发用户时,我们曾经分章节完成各个属性。但这里不再细分。
-我们来看下 `mix phoenix.gen.html` 命令生成的 `recipe_test.exs` 文件内容:
+我们来看下 `mix phx.gen.html` 命令生成的 `recipe_test.exs` 文件内容:
```elixir
defmodule TvRecipe.RecipeTest do
@@ -31,10 +31,10 @@ end
我们先增加测试:
```elixir
-diff --git a/test/models/recipe_test.exs b/test/models/recipe_test.exs
+diff --git a/test/tv_recipe/recipes/recipe_test.exs b/test/tv_recipe/recipes/recipe_test.exs
index a974aad..27f02ea 100644
---- a/test/models/recipe_test.exs
-+++ b/test/models/recipe_test.exs
+--- a/test/tv_recipe/recipes/recipe_test.exs
++++ b/test/tv_recipe/recipes/recipe_test.exs
@@ -15,4 +15,29 @@ defmodule TvRecipe.RecipeTest do
changeset = Recipe.changeset(%Recipe{}, @invalid_attrs)
refute changeset.valid?
@@ -42,37 +42,37 @@ index a974aad..27f02ea 100644
+
+ test "name is required" do
+ attrs = %{@valid_attrs | name: ""}
-+ assert {:name, "请填写"} in errors_on(%Recipe{}, attrs)
++ assert %{name: ["请填写"]} = errors_on(%Recipe{}, attrs)
+ end
+
+ test "title is required" do
+ attrs = %{@valid_attrs | title: ""}
-+ assert {:title, "请填写"} in errors_on(%Recipe{}, attrs)
++ assert %{title: ["请填写"]} = errors_on(%Recipe{}, attrs)
+ end
+
+ test "season is required" do
+ attrs = %{@valid_attrs | season: nil}
-+ assert {:season, "请填写"} in errors_on(%Recipe{}, attrs)
++ assert %{season: ["请填写"]} = errors_on(%Recipe{}, attrs)
+ end
+
+ test "episode is required" do
+ attrs = %{@valid_attrs | episode: nil}
-+ assert {:episode, "请填写"} in errors_on(%Recipe{}, attrs)
++ assert %{episode: ["请填写"]} = errors_on(%Recipe{}, attrs)
+ end
+
+ test "season should greater than 0" do
+ attrs = %{@valid_attrs | season: 0}
-+ assert {:season, "请输入大于 0 的数字"} in errors_on(%Recipe{}, attrs)
++ assert %{season: ["请输入大于 0 的数字"]} = errors_on(%Recipe{}, attrs)
+ end
+
+ test "episode should greater than 0" do
+ attrs = %{@valid_attrs | episode: 0}
-+ assert {:episode, "请输入大于 0 的数字"} in errors_on(%Recipe{}, attrs)
++ assert %{episode: ["请输入大于 0 的数字"]} = errors_on(%Recipe{}, attrs)
+ end
+
+ test "content is required" do
+ attrs = %{@valid_attrs | content: ""}
-+ assert {:content, "请填写"} in errors_on(%Recipe{}, attrs)
++ assert %{content: ["请填写"]} = errors_on(%Recipe{}, attrs)
+ end
end
```
@@ -106,13 +106,13 @@ index 946d45c..8d34ed2 100644
我们先处理 `user_id` 必填的规则,补充一个测试,如下:
```elixir
-diff --git a/test/models/recipe_test.exs b/test/models/recipe_test.exs
+diff --git a/test/tv_recipe/recipes/recipe_test.exs b/test/tv_recipe/recipes/recipe_test.exs
index 27f02ea..3a9630b 100644
---- a/test/models/recipe_test.exs
-+++ b/test/models/recipe_test.exs
+--- a/test/tv_recipe/recipes/recipe_test.exs
++++ b/test/tv_recipe/recipes/recipe_test.exs
@@ -3,7 +3,7 @@ defmodule TvRecipe.RecipeTest do
- alias TvRecipe.Recipe
+ alias TvRecipe..Recipes.Recipe
- @valid_attrs %{content: "some content", episode: 42, name: "some content", season: 42, title: "some content"}
+ @valid_attrs %{content: "some content", episode: 42, name: "some content", season: 42, title: "some content", user_id: 1}
@@ -121,12 +121,12 @@ index 27f02ea..3a9630b 100644
test "changeset with valid attributes" do
@@ -40,4 +40,9 @@ defmodule TvRecipe.RecipeTest do
attrs = %{@valid_attrs | content: ""}
- assert {:content, "请填写"} in errors_on(%Recipe{}, attrs)
+ assert %{content: ["请填写"]} = errors_on(%Recipe{}, attrs)
end
+
+ test "user_id is required" do
+ attrs = %{@valid_attrs | user_id: nil}
-+ assert {:user_id, "请填写"} in errors_on(%Recipe{}, attrs)
++ assert %{user_id: ["请填写"]} = errors_on(%Recipe{}, attrs)
+ end
end
```
@@ -151,7 +151,7 @@ index 8d34ed2..0520582 100644
运行新增的测试:
```bash
-$ mix test test/models/recipe_test.exs:54
+$ mix test test/tv_recipe/recipes/recipe_test.exs:54
Including tags: [line: "54"]
Excluding tags: [:test]
@@ -167,34 +167,35 @@ Finished in 0.1 seconds
我们在 `recipe_test.exs` 文件中再增加一个测试,确保 `user_id` 所指的用户存在:
```elixir
-diff --git a/test/models/recipe_test.exs b/test/models/recipe_test.exs
+diff --git a/test/tv_recipe/recipes/recipe_test.exs b/test/tv_recipe/recipes/recipe_test.exs
index 3a9630b..2e1191c 100644
---- a/test/models/recipe_test.exs
-+++ b/test/models/recipe_test.exs
+--- a/test/tv_recipe/recipes/recipe_test.exs
++++ b/test/tv_recipe/recipes/recipe_test.exs
@@ -1,7 +1,7 @@
defmodule TvRecipe.RecipeTest do
use TvRecipe.ModelCase
- alias TvRecipe.Recipe
-+ alias TvRecipe.{Repo, Recipe}
++ alias TvRecipe.Repo
++ alias TvRecipe.Recipes.Recipe
@valid_attrs %{content: "some content", episode: 42, name: "some content", season: 42, title: "some content", user_id: 1}
@invalid_attrs %{}
@@ -45,4 +45,9 @@ defmodule TvRecipe.RecipeTest do
attrs = %{@valid_attrs | user_id: nil}
- assert {:user_id, "请填写"} in errors_on(%Recipe{}, attrs)
+ assert %{user_id: ["请填写"]} = errors_on(%Recipe{}, attrs)
end
+
+ test "user_id should exist in users table" do
+ {:error, changeset} = Repo.insert Recipe.changeset(%Recipe{}, @valid_attrs)
-+ assert {:user_id, "用户不存在"} in errors_on(changeset)
++ assert %{user_id: ["用户不存在"]} = errors_on(changeset)
+ end
end
```
运行新增的测试:
```bash
-$ mix test test/models/recipe_test.exs:59
+$ mix test test/tv_recipe/recipes/recipe_test.exs:59
Compiling 13 files (.ex)
Including tags: [line: "59"]
Excluding tags: [:test]
@@ -202,7 +203,7 @@ Excluding tags: [:test]
1) test user_id should exist in users table (TvRecipe.RecipeTest)
- test/models/recipe_test.exs:59
+ test/tv_recipe/recipes/recipe_test.exs:59
** (Ecto.ConstraintError) constraint error when attempting to insert struct:
* foreign_key: recipes_user_id_fkey
@@ -221,7 +222,7 @@ Excluding tags: [:test]
(db_connection) lib/db_connection.ex:1274: DBConnection.transaction_run/4
(db_connection) lib/db_connection.ex:1198: DBConnection.run_begin/3
(db_connection) lib/db_connection.ex:789: DBConnection.transaction/3
- test/models/recipe_test.exs:60: (test)
+ test/tv_recipe/recipes/recipe_test.exs:60: (test)
@@ -246,7 +247,7 @@ index 0520582..a0b42fd 100644
再次运行测试:
```bash
-$ mix test test/models/recipe_test.exs:59
+$ mix test test/tv_recipe/recipes/recipe_test.exs:59
Compiling 13 files (.ex)
Including tags: [line: "59"]
Excluding tags: [:test]
diff --git a/07-recipe/03-recipe-controller.md b/07-recipe/03-recipe-controller.md
index d550164..a5d5a5d 100644
--- a/07-recipe/03-recipe-controller.md
+++ b/07-recipe/03-recipe-controller.md
@@ -9,66 +9,93 @@
一个粗暴的解决办法,是在每个测试中新建一个用户,然后把用户 id 传给 `@valid_attrs`,但那样又要重复一堆代码,我们可以把新建用户部分抽取到 `setup` 中:
```elixir
-diff --git a/test/controllers/recipe_controller_test.exs b/test/controllers/recipe_controller_test.exs
-index 646ebf2..51fdeab 100644
---- a/test/controllers/recipe_controller_test.exs
-+++ b/test/controllers/recipe_controller_test.exs
-@@ -1,10 +1,16 @@
- defmodule TvRecipe.RecipeControllerTest do
- use TvRecipe.ConnCase
-
-- alias TvRecipe.Recipe
-+ alias TvRecipe.{Repo, User, Recipe}
- @valid_attrs %{content: "some content", episode: 42, name: "some content", season: 42, title: "some content"}
- @invalid_attrs %{}
-
-+ setup do
-+ user = Repo.insert! User.changeset(%User{}, %{email: "chenxsan@gmail.com", username: "chenxsan", password: String.duplicate("1", 6)})
-+ attrs = Map.put(@valid_attrs, :user_id, user.id)
-+ {:ok, [attrs: attrs]}
-+ end
+diff --git a/test/tv_recipe_web/controllers/recipe_controller_test.exs b/test/tv_recipe_web/controllers/recipe_controller_test.exs
+index 923a4a9..0548c85 100644
+--- a/test/tv_recipe_web/controllers/recipe_controller_test.exs
++++ b/test/tv_recipe_web/controllers/recipe_controller_test.exs
+@@ -2,17 +2,29 @@ defmodule TvRecipeWeb.RecipeControllerTest do
+ use TvRecipeWeb.ConnCase
+
+ alias TvRecipe.Recipes
++ alias TvRecipe.Repo
++ alias TvRecipe.Users.User
++ alias TvRecipe.Recipes.Recipe
+
+ @create_attrs %{content: "some content", episode: 42, name: "some name", season: 42, title: "some title"}
+ @update_attrs %{content: "some updated content", episode: 43, name: "some updated name", season: 43, title: "some updated title"}
+ @invalid_attrs %{content: nil, episode: nil, name: nil, season: nil, title: nil}
+
++ defp init_attrs (%{conn: conn} = context) do
++ user = Repo.insert! User.changeset(%User{}, %{email: "chenxsan@gmail.com", username: "chenxsan", password: String.duplicate("1", 6)})
++ attrs = Map.put(@create_attrs, :user_id, user.id)
+
- test "lists all entries on index", %{conn: conn} do
- conn = get conn, recipe_path(conn, :index)
- assert html_response(conn, 200) =~ "Listing recipes"
-@@ -15,10 +21,10 @@ defmodule TvRecipe.RecipeControllerTest do
- assert html_response(conn, 200) =~ "New recipe"
- end
-+ user = Repo.insert! User.changeset(%User{}, %{email: "chenxsan@gmail.com", username: "chenxsan", password: String.duplicate("1", 6)})
-+ attrs = Map.put(@valid_attrs, :user_id, user.id)
-+ {:ok, [attrs: attrs]}
++ context
++ |> Map.put(:attrs, attrs)
+ end
+
- test "lists all entries on index", %{conn: conn} do
- conn = get conn, recipe_path(conn, :index)
- assert html_response(conn, 200) =~ "Listing recipes"
-@@ -15,10 +21,10 @@ defmodule TvRecipe.RecipeControllerTest do
- assert html_response(conn, 200) =~ "New recipe"
- end
-
-- test "creates resource and redirects when data is valid", %{conn: conn} do
-- conn = post conn, recipe_path(conn, :create), recipe: @valid_attrs
-+ test "creates resource and redirects when data is valid", %{conn: conn, attrs: attrs} do
-+ conn = post conn, recipe_path(conn, :create), recipe: attrs
- assert redirected_to(conn) == recipe_path(conn, :index)
-- assert Repo.get_by(Recipe, @valid_attrs)
-+ assert Repo.get_by(Recipe, attrs)
- end
-
- test "does not create resource and renders errors when data is invalid", %{conn: conn} do
-@@ -44,11 +50,11 @@ defmodule TvRecipe.RecipeControllerTest do
- assert html_response(conn, 200) =~ "Edit recipe"
+ def fixture(attrs) do
+ {:ok, recipe} = Recipes.create_recipe(attrs)
+ recipe
+ end
+
+ describe "index" do
++ setup [:init_attrs]
+ test "lists all recipes", %{conn: conn} do
+ conn = get(conn, Routes.recipe_path(conn, :index))
+ assert html_response(conn, 200) =~ "Listing Recipes"
+@@ -27,8 +39,9 @@ defmodule TvRecipeWeb.RecipeControllerTest do
+ end
+
+ describe "create recipe" do
++ setup [:init_attrs]
+- test "redirects to show when data is valid", %{conn: conn} do
++ test "redirects to show when data is valid", %{conn: conn, attrs: attrs} do
+- conn = post(conn, Routes.recipe_path(conn, :create), recipe: @create_attrs)
++ conn = post(conn, Routes.recipe_path(conn, :create), recipe: attrs)
+
+ assert %{id: id} = redirected_params(conn)
+ assert redirected_to(conn) == Routes.recipe_path(conn, :show, id)
+@@ -44,7 +57,7 @@ defmodule TvRecipeWeb.RecipeControllerTest do
+ end
+
+ describe "edit recipe" do
+- setup [:create_recipe]
++ setup [:init_attrs, :create_recipe]
+
+ test "renders form for editing chosen recipe", %{conn: conn, recipe: recipe} do
+ conn = get(conn, Routes.recipe_path(conn, :edit, recipe))
+@@ -53,7 +66,7 @@ defmodule TvRecipeWeb.RecipeControllerTest do
+ end
+
+ describe "update recipe" do
+- setup [:create_recipe]
++ setup [:init_attrs, :create_recipe]
+
+ test "redirects when data is valid", %{conn: conn, recipe: recipe} do
+ conn = put(conn, Routes.recipe_path(conn, :update, recipe), recipe: @update_attrs)
+@@ -70,7 +83,7 @@ defmodule TvRecipeWeb.RecipeControllerTest do
+ end
+
+ describe "delete recipe" do
+- setup [:create_recipe]
++ setup [:init_attrs, :create_recipe]
+
+ test "deletes chosen recipe", %{conn: conn, recipe: recipe} do
+ conn = delete(conn, Routes.recipe_path(conn, :delete, recipe))
+@@ -81,8 +94,10 @@ defmodule TvRecipeWeb.RecipeControllerTest do
+ end
end
-
-- test "updates chosen resource and redirects when data is valid", %{conn: conn} do
-+ test "updates chosen resource and redirects when data is valid", %{conn: conn, attrs: attrs} do
- recipe = Repo.insert! %Recipe{}
-- conn = put conn, recipe_path(conn, :update, recipe), recipe: @valid_attrs
-+ conn = put conn, recipe_path(conn, :update, recipe), recipe: attrs
- assert redirected_to(conn) == recipe_path(conn, :show, recipe)
-- assert Repo.get_by(Recipe, @valid_attrs)
-+ assert Repo.get_by(Recipe, attrs)
+
+- defp create_recipe(_) do
++ defp create_recipe(%{attrs: attrs} = context) do
+- recipe = fixture(:recipe)
++ recipe = fixture(attrs)
++
+- %{recipe: recipe}
++ context
++ |> Map.put(:recipe, recipe)
end
+ end
```
在 `setup` 块中,我们新建了一个用户,并且重新组合出真正有效的 recipe 属性 `attrs`,然后返回。
@@ -101,103 +128,82 @@ delete|需要
都要登录?难道未登录用户不能查看其它用户创建的菜谱?当然可以,但我们将新建路由来满足这些需求。这一节,我们开发的是 Recipe 相关的管理动作。
-前面章节中我们已经尝试过使用 `tag` 来标注用户登录状态下的测试,现在根据上面罗列的需求来修改 `recipe_controller_test.exs` 文件中的测试:
+前面章节中我们已经尝试过使用 `setup [:login_user]` 来标注用户登录状态下的测试,现在根据上面罗列的需求来修改 `recipe_controller_test.exs` 文件中的测试:
```elixir
-diff --git a/test/controllers/recipe_controller_test.exs b/test/controllers/recipe_controller_test.exs
-index 51fdeab..5632f8c 100644
---- a/test/controllers/recipe_controller_test.exs
-+++ b/test/controllers/recipe_controller_test.exs
-@@ -5,51 +5,65 @@ defmodule TvRecipe.RecipeControllerTest do
- @valid_attrs %{content: "some content", episode: 42, name: "some content", season: 42, title: "some content"}
- @invalid_attrs %{}
-
-- setup do
-- user = Repo.insert! User.changeset(%User{}, %{email: "chenxsan@gmail.com", username: "chenxsan", password: String.duplicate("1", 6)})
-- attrs = Map.put(@valid_attrs, :user_id, user.id)
-- {:ok, [attrs: attrs]}
-+ setup %{conn: conn} = context do
+diff --git a/test/tv_recipe_web/controllers/recipe_controller_test.exs b/test/tv_recipe_web/controllers/recipe_controller_test.exs
+index 0548c85..5eb8866 100644
+--- a/test/tv_recipe_web/controllers/recipe_controller_test.exs
++++ b/test/tv_recipe_web/controllers/recipe_controller_test.exs
+@@ -10,8 +10,18 @@ defmodule TvRecipeWeb.RecipeControllerTest do
+ @update_attrs %{content: "some updated content", episode: 43, name: "some updated name", season: 43, title: "some updated title"}
+ @invalid_attrs %{content: nil, episode: nil, name: nil, season: nil, title: nil}
+
++ defp login_user(%{conn: conn} = context) do
+ user_attrs = %{email: "chenxsan@gmail.com", username: "chenxsan", password: String.duplicate("1", 6)}
+ user = Repo.insert! User.changeset(%User{}, user_attrs)
-+ attrs = Map.put(@valid_attrs, :user_id, user.id)
-+ if context[:logged_in] == true do
-+ conn = post conn, session_path(conn, :create), session: user_attrs
-+ {:ok, [conn: conn, attrs: attrs]}
-+ else
-+ {:ok, [attrs: attrs]}
-+ end
- end
--
++ attrs = Map.put(@create_attrs, :user_id, user.id)
++ conn = post conn, Routes.session_path(conn, :create), session: user_attrs
+
-+ @tag logged_in: true
- test "lists all entries on index", %{conn: conn} do
- conn = get conn, recipe_path(conn, :index)
- assert html_response(conn, 200) =~ "Listing recipes"
- end
-
-+ @tag logged_in: true
- test "renders form for new resources", %{conn: conn} do
- conn = get conn, recipe_path(conn, :new)
- assert html_response(conn, 200) =~ "New recipe"
- end
-
-+ @tag logged_in: true
- test "creates resource and redirects when data is valid", %{conn: conn, attrs: attrs} do
- conn = post conn, recipe_path(conn, :create), recipe: attrs
- assert redirected_to(conn) == recipe_path(conn, :index)
- assert Repo.get_by(Recipe, attrs)
- end
-
-+ @tag logged_in: true
- test "does not create resource and renders errors when data is invalid", %{conn: conn} do
- conn = post conn, recipe_path(conn, :create), recipe: @invalid_attrs
- assert html_response(conn, 200) =~ "New recipe"
- end
-
-+ @tag logged_in: true
- test "shows chosen resource", %{conn: conn} do
- recipe = Repo.insert! %Recipe{}
- conn = get conn, recipe_path(conn, :show, recipe)
- assert html_response(conn, 200) =~ "Show recipe"
- end
-
-+ @tag logged_in: true
-+ @tag logged_in: true
- test "renders page not found when id is nonexistent", %{conn: conn} do
- assert_error_sent 404, fn ->
- get conn, recipe_path(conn, :show, -1)
- end
- end
-
-+ @tag logged_in: true
- test "renders form for editing chosen resource", %{conn: conn} do
- recipe = Repo.insert! %Recipe{}
- conn = get conn, recipe_path(conn, :edit, recipe)
- assert html_response(conn, 200) =~ "Edit recipe"
- end
-
-+ @tag logged_in: true
- test "updates chosen resource and redirects when data is valid", %{conn: conn, attrs: attrs} do
- recipe = Repo.insert! %Recipe{}
- conn = put conn, recipe_path(conn, :update, recipe), recipe: attrs
-@@ -57,12 +71,14 @@ defmodule TvRecipe.RecipeControllerTest do
- assert Repo.get_by(Recipe, attrs)
- end
-
-+ @tag logged_in: true
- test "does not update chosen resource and renders errors when data is invalid", %{conn: conn} do
- recipe = Repo.insert! %Recipe{}
- conn = put conn, recipe_path(conn, :update, recipe), recipe: @invalid_attrs
- assert html_response(conn, 200) =~ "Edit recipe"
- end
-
-+ @tag logged_in: true
- test "deletes chosen resource", %{conn: conn} do
- recipe = Repo.insert! %Recipe{}
- conn = delete conn, recipe_path(conn, :delete, recipe)
-```
-
-我们给所有测试代码都加上了 `@tag logged_in` 的标签。
++ context
++ |> Map.put(:conn, conn)
++ |> Map.put(:user, user)
++ end
++
+- defp init_attrs (%{conn: conn} = context) do
++ defp init_attrs (%{user: user} = context) do
+- user = Repo.insert! User.changeset(%User{}, %{email: "chenxsan@gmail.com", username: "chenxsan", password: String.duplicate("1", 6)})
+ attrs = Map.put(@create_attrs, :user_id, user.id)
+
+ context
+@@ -24,7 +34,7 @@ defmodule TvRecipeWeb.RecipeControllerTest do
+ end
+
+ describe "index" do
+- setup [:init_attrs]
++ setup [:login_user, :init_attrs]
+ test "lists all recipes", %{conn: conn} do
+ conn = get(conn, Routes.recipe_path(conn, :index))
+ assert html_response(conn, 200) =~ "Listing Recipes"
+@@ -39,7 +49,7 @@ defmodule TvRecipeWeb.RecipeControllerTest do
+ end
+
+ describe "create recipe" do
+- setup [:init_attrs]
++ setup [:login_user, :init_attrs]
+ test "redirects to show when data is valid", %{conn: conn, attrs: attrs} do
+ conn = post(conn, Routes.recipe_path(conn, :create), recipe: attrs)
+
+@@ -57,7 +67,7 @@ defmodule TvRecipeWeb.RecipeControllerTest do
+ end
+
+ describe "edit recipe" do
+- setup [:init_attrs, :create_recipe]
++ setup [:login_user, :init_attrs, :create_recipe]
+
+ test "renders form for editing chosen recipe", %{conn: conn, recipe: recipe} do
+ conn = get(conn, Routes.recipe_path(conn, :edit, recipe))
+@@ -66,7 +76,7 @@ defmodule TvRecipeWeb.RecipeControllerTest do
+ end
+
+ describe "update recipe" do
+- setup [:init_attrs, :create_recipe]
++ setup [:login_user, :init_attrs, :create_recipe]
+
+ test "redirects when data is valid", %{conn: conn, recipe: recipe} do
+ conn = put(conn, Routes.recipe_path(conn, :update, recipe), recipe: @update_attrs)
+@@ -83,7 +93,7 @@ defmodule TvRecipeWeb.RecipeControllerTest do
+ end
+
+ describe "delete recipe" do
+- setup [:init_attrs, :create_recipe]
++ setup [:login_user, :init_attrs, :create_recipe]
+
+ test "deletes chosen recipe", %{conn: conn, recipe: recipe} do
+ conn = delete(conn, Routes.recipe_path(conn, :delete, recipe))
+```
+
+我们给所有测试代码都加上了 `setup [:login_user, :init_attrs]` 的设置。
接下来我们需要一个验证用户登录状态的 plug,不巧我们在 `user_controller.ex` 文件中已经定义了一个 `login_require` 的 plug,现在是其它地方也要用到它 - 再放在 `user_controller.ex` 中并不合适,我们将它移到 `auth.ex` 文件中:
@@ -207,14 +213,14 @@ index e298b68..3dd3e7f 100644
--- a/web/controllers/auth.ex
+++ b/web/controllers/auth.ex
@@ -1,5 +1,7 @@
- defmodule TvRecipe.Auth do
+ defmodule TvRecipeWeb.Auth do
import Plug.Conn
+ import Phoenix.Controller
-+ alias TvRecipe.Router.Helpers
++ use TvRecipeWeb, :controller
@doc """
初始化选项
-@@ -21,4 +23,37 @@ defmodule TvRecipe.Auth do
+@@ -21,4 +23,37 @@ defmodule TvRecipeWeb.Auth do
|> configure_session(renew: true)
end
@@ -229,7 +235,7 @@ index e298b68..3dd3e7f 100644
+ else
+ conn
+ |> put_flash(:info, "请先登录")
-+ |> redirect(to: Helpers.session_path(conn, :new))
++ |> redirect(to: Routes.session_path(conn, :new))
+ |> halt()
+ end
+ end
@@ -246,7 +252,7 @@ index e298b68..3dd3e7f 100644
+ else
+ conn
+ |> put_flash(:error, "禁止访问未授权页面")
-+ |> redirect(to: Helpers.user_path(conn, :show, conn.assigns.current_user))
++ |> redirect(to: Routes.user_path(conn, :show, conn.assigns.current_user))
+ |> halt()
+ end
+ end
@@ -272,7 +278,7 @@ index 520d986..0f023d3 100644
- else
- conn
- |> put_flash(:info, "请先登录")
-- |> redirect(to: session_path(conn, :new))
+- |> redirect(to: Routes.session_path(conn, :new))
- |> halt()
- end
- end
@@ -291,7 +297,7 @@ index 520d986..0f023d3 100644
- else
- conn
- |> put_flash(:info, "请先登录")
-- |> redirect(to: session_path(conn, :new))
+- |> redirect(to: Routes.session_path(conn, :new))
- |> halt()
- end
- end
@@ -308,7 +314,7 @@ index 520d986..0f023d3 100644
- else
- conn
- |> put_flash(:error, "禁止访问未授权页面")
-- |> redirect(to: user_path(conn, :show, conn.assigns.current_user))
+- |> redirect(to: Routes.user_path(conn, :show, conn.assigns.current_user))
- |> halt()
- end
- end
@@ -317,23 +323,20 @@ index 520d986..0f023d3 100644
注意,我们并非只是简单的移动文本到 `auth.ex` 文件中。在 `auth.ex` 头部,我们还引入了两行代码,并调整了两个 plug:
```elixir
-import Phoenix.Controller
-alias TvRecipe.Router.Helpers
+ import Phoenix.Controller
+ use TvRecipeWeb, :controller
```
-`import Phoenix.Controller` 导入 `put_flash` 等方法,而 `alias TvRecipe.Router.Helpers` 让我们在 `auth.ex` 中可以快速书写各种路径。
+`import Phoenix.Controller` 导入 `put_flash` 等方法,而 `use TvRecipeWeb, :controller` 让我们在 `auth.ex` 中可以快速书写各种路径。
-接着在 `web.ex` 文件中 `import` 它:
+接着在 `user_controller.ex` 文件中 `import` 它:
```elixir
-diff --git a/web/web.ex b/web/web.ex
+diff --git a/web/controllers/user_controller.ex b/web/controllers/user_controller.ex
index 50fd62e..9990080 100644
--- a/web/web.ex
+++ b/web/web.ex
@@ -36,6 +36,7 @@ defmodule TvRecipe.Web do
-
- import TvRecipe.Router.Helpers
- import TvRecipe.Gettext
-+ import TvRecipe.Auth, only: [login_require: 2, self_require: 2]
++ import TvRecipeWeb.Auth, only: [login_require: 2, self_require: 2]
end
end
```
@@ -347,8 +350,9 @@ index 96a0276..c74b492 100644
--- a/web/controllers/recipe_controller.ex
+++ b/web/controllers/recipe_controller.ex
@@ -1,6 +1,6 @@
- defmodule TvRecipe.RecipeController do
- use TvRecipe.Web, :controller
+ defmodule TvRecipeWeb.RecipeController do
+ use TvRecipeWeb, :controller
+ import TvRecipeWeb.Auth, only: [login_require: 2]
-
+ plug :login_require
alias TvRecipe.Recipe
@@ -375,7 +379,7 @@ diff --git a/test/controllers/recipe_controller_test.exs b/test/controllers/reci
index 5632f8c..faf67ca 100644
--- a/test/controllers/recipe_controller_test.exs
+++ b/test/controllers/recipe_controller_test.exs
-@@ -16,7 +16,7 @@ defmodule TvRecipe.RecipeControllerTest do
+@@ -16,7 +16,7 @@ defmodule TvRecipeWeb.RecipeControllerTest do
{:ok, [attrs: attrs]}
end
end
@@ -383,24 +387,24 @@ index 5632f8c..faf67ca 100644
+
@tag logged_in: true
test "lists all entries on index", %{conn: conn} do
- conn = get conn, recipe_path(conn, :index)
-@@ -85,4 +85,20 @@ defmodule TvRecipe.RecipeControllerTest do
- assert redirected_to(conn) == recipe_path(conn, :index)
+ conn = get conn, Routes.recipe_path(conn, :index)
+@@ -85,4 +85,20 @@ defmodule TvRecipeWeb.RecipeControllerTest do
+ assert redirected_to(conn) == Routes.recipe_path(conn, :index)
refute Repo.get(Recipe, recipe.id)
end
+
+ test "guest access user action redirected to login page", %{conn: conn} do
+ recipe = Repo.insert! %Recipe{}
+ Enum.each([
-+ get(conn, recipe_path(conn, :index)),
-+ get(conn, recipe_path(conn, :new)),
-+ get(conn, recipe_path(conn, :create), recipe: %{}),
-+ get(conn, recipe_path(conn, :show, recipe)),
-+ get(conn, recipe_path(conn, :edit, recipe)),
-+ put(conn, recipe_path(conn, :update, recipe), recipe: %{}),
-+ put(conn, recipe_path(conn, :delete, recipe)),
++ get(conn, Routes.recipe_path(conn, :index)),
++ get(conn, Routes.recipe_path(conn, :new)),
++ get(conn, Routes.recipe_path(conn, :create), recipe: %{}),
++ get(conn, Routes.recipe_path(conn, :show, recipe)),
++ get(conn, Routes.recipe_path(conn, :edit, recipe)),
++ put(conn, Routes.recipe_path(conn, :update, recipe), recipe: %{}),
++ put(conn, Routes.recipe_path(conn, :delete, recipe)),
+ ], fn conn ->
-+ assert redirected_to(conn) == session_path(conn, :new)
++ assert redirected_to(conn) == Routes.session_path(conn, :new)
+ assert conn.halted
+ end)
+ end
@@ -427,18 +431,17 @@ diff --git a/test/controllers/recipe_controller_test.exs b/test/controllers/reci
index faf67ca..d8157a2 100644
--- a/test/controllers/recipe_controller_test.exs
+++ b/test/controllers/recipe_controller_test.exs
-@@ -101,4 +101,17 @@ defmodule TvRecipe.RecipeControllerTest do
+@@ -101,4 +101,17 @@ defmodule TvRecipeWeb.RecipeControllerTest do
assert conn.halted
end)
end
+
-+ @tag logged_in: true
+ test "creates resource and redirects when data is valid but with other user_id", %{conn: conn, attrs: attrs} do
+ # 新建一个用户
+ user = Repo.insert! User.changeset(%User{}, %{email: "chenxsan+1@gmail.com", username: "samchen", password: String.duplicate("1", 6)})
+ # 将新用户的 id 更新入 attrs,尝试替 samchen 创建一个菜谱
+ new_attrs = %{attrs | user_id: user.id}
-+ post conn, recipe_path(conn, :create), recipe: new_attrs
++ post conn, Routes.recipe_path(conn, :create), recipe: new_attrs
+ # 用户 chenxsan 只能创建自己的菜谱,无法替 samchen 创建菜谱
+ assert Repo.get_by(Recipe, attrs)
+ # samchen 不应该有菜谱
@@ -452,7 +455,7 @@ index faf67ca..d8157a2 100644
$ mix test
..........................
- 1) test creates resource and redirects when data is valid but with other user_id (TvRecipe.RecipeControllerTest)
+ 1) test creates resource and redirects when data is valid but with other user_id (TvRecipeWeb.RecipeControllerTest)
test/controllers/recipe_controller_test.exs:106
Expected truthy, got nil
code: Repo.get_by(Recipe, attrs)
@@ -534,54 +537,44 @@ diff --git a/test/controllers/recipe_controller_test.exs b/test/controllers/reci
index d8157a2..d953315 100644
--- a/test/controllers/recipe_controller_test.exs
+++ b/test/controllers/recipe_controller_test.exs
-@@ -7,13 +7,12 @@ defmodule TvRecipe.RecipeControllerTest do
+@@ -7,13 +7,12 @@ defmodule TvRecipeWeb.RecipeControllerTest do
- setup %{conn: conn} = context do
- user_attrs = %{email: "chenxsan@gmail.com", username: "chenxsan", password: String.duplicate("1", 6)}
-- user = Repo.insert! User.changeset(%User{}, user_attrs)
+- defp init_attrs(%{user: user} = context) do
++ defp init_attrs(%{conn: conn} = context) do
- attrs = Map.put(@valid_attrs, :user_id, user.id)
-+ Repo.insert! User.changeset(%User{}, user_attrs)
- if context[:logged_in] == true do
- conn = post conn, session_path(conn, :create), session: user_attrs
-- {:ok, [conn: conn, attrs: attrs]}
-+ {:ok, [conn: conn]}
- else
-- {:ok, [attrs: attrs]}
-+ :ok
- end
+-
+ context
+ |> Map.put :attrs, @@valid_attrs
end
-@@ -30,10 +29,10 @@ defmodule TvRecipe.RecipeControllerTest do
+@@ -30,10 +29,10 @@ defmodule TvRecipeWeb.RecipeControllerTest do
end
- @tag logged_in: true
- test "creates resource and redirects when data is valid", %{conn: conn, attrs: attrs} do
-- conn = post conn, recipe_path(conn, :create), recipe: attrs
+- conn = post conn, Routes.recipe_path(conn, :create), recipe: attrs
+ test "creates resource and redirects when data is valid", %{conn: conn} do
-+ conn = post conn, recipe_path(conn, :create), recipe: @valid_attrs
- assert redirected_to(conn) == recipe_path(conn, :index)
++ conn = post conn, Routes.recipe_path(conn, :create), recipe: @valid_attrs
+ assert redirected_to(conn) == Routes.recipe_path(conn, :index)
- assert Repo.get_by(Recipe, attrs)
+ assert Repo.get_by(Recipe, @valid_attrs)
end
- @tag logged_in: true
-@@ -64,11 +63,11 @@ defmodule TvRecipe.RecipeControllerTest do
-@@ -64,11 +63,11 @@ defmodule TvRecipe.RecipeControllerTest do
+@@ -64,11 +63,11 @@ defmodule TvRecipeWeb.RecipeControllerTest do
+@@ -64,11 +63,11 @@ defmodule TvRecipeWeb.RecipeControllerTest do
end
- @tag logged_in: true
- test "updates chosen resource and redirects when data is valid", %{conn: conn, attrs: attrs} do
+ test "updates chosen resource and redirects when data is valid", %{conn: conn} do
recipe = Repo.insert! %Recipe{}
-- conn = put conn, recipe_path(conn, :update, recipe), recipe: attrs
-+ conn = put conn, recipe_path(conn, :update, recipe), recipe: @valid_attrs
- assert redirected_to(conn) == recipe_path(conn, :show, recipe)
+- conn = put conn, Routes.recipe_path(conn, :update, recipe), recipe: attrs
++ conn = put conn, Routes.recipe_path(conn, :update, recipe), recipe: @valid_attrs
+ assert redirected_to(conn) == Routes.recipe_path(conn, :show, recipe)
- assert Repo.get_by(Recipe, attrs)
+ assert Repo.get_by(Recipe, @valid_attrs)
end
- @tag logged_in: true
-@@ -102,16 +101,4 @@ defmodule TvRecipe.RecipeControllerTest do
+
+@@ -102,16 +101,4 @@ defmodule TvRecipeWeb.RecipeControllerTest do
end)
end
@@ -591,7 +584,7 @@ index d8157a2..d953315 100644
- user = Repo.insert! User.changeset(%User{}, %{email: "chenxsan+1@gmail.com", username: "samchen", password: String.duplicate("1", 6)})
- # 将新用户的 id 更新入 attrs,尝试替 samchen 创建一个菜谱
- new_attrs = %{attrs | user_id: user.id}
-- post conn, recipe_path(conn, :create), recipe: new_attrs
+- post conn, Routes.recipe_path(conn, :create), recipe: new_attrs
- # 用户 chenxsan 只能创建自己的菜谱,无法替 samchen 创建菜谱
- assert Repo.get_by(Recipe, attrs)
- # samchen 不应该有菜谱
@@ -613,6 +606,16 @@ index c74b492..967b7bc 100644
@@ -9,12 +9,18 @@ defmodule TvRecipe.RecipeController do
end
+ def index(conn, _params) do
+- recipes = Recipes.list_recipes()
++ recipes =
++ conn.assigns.current_user
++ |> assoc(:recipes)
++ |> TvRecipe.Repo.all()
+
+ render(conn, "index.html", recipes: recipes)
+ end
+
def new(conn, _params) do
- changeset = Recipe.changeset(%Recipe{})
+ changeset =
@@ -662,32 +665,17 @@ diff --git a/test/controllers/recipe_controller_test.exs b/test/controllers/reci
index d953315..b901b61 100644
--- a/test/controllers/recipe_controller_test.exs
+++ b/test/controllers/recipe_controller_test.exs
-@@ -7,10 +7,10 @@ defmodule TvRecipe.RecipeControllerTest do
-
- setup %{conn: conn} = context do
- user_attrs = %{email: "chenxsan@gmail.com", username: "chenxsan", password: String.duplicate("1", 6)}
-- Repo.insert! User.changeset(%User{}, user_attrs)
-+ user = Repo.insert! User.changeset(%User{}, user_attrs)
- if context[:logged_in] == true do
- conn = post conn, session_path(conn, :create), session: user_attrs
-- {:ok, [conn: conn]}
-+ {:ok, [conn: conn, user: user]}
- else
- :ok
- end
-@@ -29,10 +29,10 @@ defmodule TvRecipe.RecipeControllerTest do
+@@ -29,10 +29,10 @@ defmodule TvRecipeWeb.RecipeControllerTest do
end
- @tag logged_in: true
- test "creates resource and redirects when data is valid", %{conn: conn} do
+ test "creates resource and redirects when data is valid", %{conn: conn, user: user} do
- conn = post conn, recipe_path(conn, :create), recipe: @valid_attrs
- assert redirected_to(conn) == recipe_path(conn, :index)
+ conn = post conn, Routes.recipe_path(conn, :create), recipe: @valid_attrs
+ assert redirected_to(conn) == Routes.recipe_path(conn, :index)
- assert Repo.get_by(Recipe, @valid_attrs)
+ assert Repo.get_by(Recipe, Map.put(@valid_attrs, :user_id, user.id))
end
- @tag logged_in: true
```
运行测试:
@@ -711,23 +699,22 @@ diff --git a/test/controllers/recipe_controller_test.exs b/test/controllers/reci
index b901b61..cdbc420 100644
--- a/test/controllers/recipe_controller_test.exs
+++ b/test/controllers/recipe_controller_test.exs
-@@ -101,4 +101,19 @@ defmodule TvRecipe.RecipeControllerTest do
+@@ -101,4 +101,19 @@ defmodule TvRecipeWeb.RecipeControllerTest do
end)
end
-+ @tag logged_in: true
+ test "user should not allowed to show recipe of other people", %{conn: conn, user: user} do
+ # 当前登录用户创建了一个菜谱
-+ conn = post conn, recipe_path(conn, :create), recipe: @valid_attrs
++ conn = post conn, Routes.recipe_path(conn, :create), recipe: @valid_attrs
+ recipe = Repo.get_by(Recipe, Map.put(@valid_attrs, :user_id, user.id))
+ # 新建一个用户
+ new_user_attrs = %{email: "chenxsan+1@gmail.com", "username": "samchen", password: String.duplicate("1", 6)}
+ Repo.insert! User.changeset(%User{}, new_user_attrs)
+ # 登录新建的用户
-+ conn = post conn, session_path(conn, :create), session: new_user_attrs
++ conn = post conn, Routes.session_path(conn, :create), session: new_user_attrs
+ # 读取前头的 recipe 失败,因为它不属于新用户所有
+ assert_error_sent 404, fn ->
-+ get conn, recipe_path(conn, :show, recipe)
++ get conn, Routes.recipe_path(conn, :show, recipe)
+ end
+ end
end
@@ -739,7 +726,7 @@ mix test
Compiling 1 file (.ex)
................................
- 1) test user should not allowed to show recipe of other people (TvRecipe.RecipeControllerTest)
+ 1) test user should not allowed to show recipe of other people (TvRecipeWeb.RecipeControllerTest)
test/controllers/recipe_controller_test.exs:105
expected error to be sent as 404 status, but response sent 200 without error
stacktrace:
@@ -771,7 +758,7 @@ index 967b7bc..22554ea 100644
def show(conn, %{"id" => id}) do
- recipe = Repo.get!(Recipe, id)
-+ recipe = Repo.get!(assoc(conn.assigns.current_user, :recipes), id)
++ recipe = assoc(conn.assigns.current_user, :recipes) |> Repo.get!(id)
render(conn, "show.html", recipe: recipe)
end
```
@@ -781,7 +768,7 @@ index 967b7bc..22554ea 100644
$ mix test
..........................
- 1) test shows chosen resource (TvRecipe.RecipeControllerTest)
+ 1) test shows chosen resource (TvRecipeWeb.RecipeControllerTest)
test/controllers/recipe_controller_test.exs:45
** (Ecto.NoResultsError) expected at least one result but got none in query:
@@ -816,7 +803,7 @@ diff --git a/test/controllers/recipe_controller_test.exs b/test/controllers/reci
index cdbc420..d93bbd1 100644
--- a/test/controllers/recipe_controller_test.exs
+++ b/test/controllers/recipe_controller_test.exs
-@@ -42,8 +42,8 @@ defmodule TvRecipe.RecipeControllerTest do
+@@ -42,8 +42,8 @@ defmodule TvRecipeWeb.RecipeControllerTest do
end
@tag logged_in: true
@@ -824,7 +811,7 @@ index cdbc420..d93bbd1 100644
- recipe = Repo.insert! %Recipe{}
+ test "shows chosen resource", %{conn: conn, user: user} do
+ recipe = Repo.insert! %Recipe{user_id: user.id}
- conn = get conn, recipe_path(conn, :show, recipe)
+ conn = get conn, Routes.recipe_path(conn, :show, recipe)
assert html_response(conn, 200) =~ "Show recipe"
end
```
@@ -851,7 +838,7 @@ index 22554ea..f317b59 100644
def index(conn, _params) do
- recipes = Repo.all(Recipe)
-+ recipes = Repo.all(assoc(conn.assigns.current_user, :recipes))
++ recipes = assoc(conn.assigns.current_user, :recipes) |> Repo.all()
render(conn, "index.html", recipes: recipes)
end
@@ -860,14 +847,14 @@ index 22554ea..f317b59 100644
def edit(conn, %{"id" => id}) do
- recipe = Repo.get!(Recipe, id)
-+ recipe = Repo.get!(assoc(conn.assigns.current_user, :recipes), id)
++ recipe = assoc(conn.assigns.current_user, :recipes) |> Repo.get!(id)
changeset = Recipe.changeset(recipe)
render(conn, "edit.html", recipe: recipe, changeset: changeset)
end
def update(conn, %{"id" => id, "recipe" => recipe_params}) do
- recipe = Repo.get!(Recipe, id)
-+ recipe = Repo.get!(assoc(conn.assigns.current_user, :recipes), id)
++ recipe = assoc(conn.assigns.current_user, :recipes) |> Repo.get!(id)
changeset = Recipe.changeset(recipe, recipe_params)
case Repo.update(changeset) do
@@ -876,7 +863,7 @@ index 22554ea..f317b59 100644
def delete(conn, %{"id" => id}) do
- recipe = Repo.get!(Recipe, id)
-+ recipe = Repo.get!(assoc(conn.assigns.current_user, :recipes), id)
++ recipe = assoc(conn.assigns.current_user, :recipes) |> Repo.get!(id)
# Here we use delete! (with a bang) because we expect
# it to always work (and if it does not, it will raise).
@@ -888,50 +875,17 @@ diff --git a/test/controllers/recipe_controller_test.exs b/test/controllers/reci
index d93bbd1..190ede9 100644
--- a/test/controllers/recipe_controller_test.exs
+++ b/test/controllers/recipe_controller_test.exs
-@@ -56,30 +56,30 @@ defmodule TvRecipe.RecipeControllerTest do
- end
-
- @tag logged_in: true
-- test "renders form for editing chosen resource", %{conn: conn} do
-- recipe = Repo.insert! %Recipe{}
- end
-
- @tag logged_in: true
-- test "renders form for editing chosen resource", %{conn: conn} do
-- recipe = Repo.insert! %Recipe{}
-+ test "renders form for editing chosen resource", %{conn: conn, user: user} do
-+ recipe = Repo.insert! %Recipe{user_id: user.id}
- conn = get conn, recipe_path(conn, :edit, recipe)
- assert html_response(conn, 200) =~ "Edit recipe"
- end
-
- @tag logged_in: true
-- test "updates chosen resource and redirects when data is valid", %{conn: conn} do
-- recipe = Repo.insert! %Recipe{}
-+ test "updates chosen resource and redirects when data is valid", %{conn: conn, user: user} do
-+ recipe = Repo.insert! %Recipe{user_id: user.id}
- conn = put conn, recipe_path(conn, :update, recipe), recipe: @valid_attrs
- assert redirected_to(conn) == recipe_path(conn, :show, recipe)
- assert Repo.get_by(Recipe, @valid_attrs)
- end
-
- @tag logged_in: true
-- test "does not update chosen resource and renders errors when data is invalid", %{conn: conn} do
-- recipe = Repo.insert! %Recipe{}
-+ test "does not update chosen resource and renders errors when data is invalid", %{conn: conn, user: user} do
-+ recipe = Repo.insert! %Recipe{user_id: user.id}
- conn = put conn, recipe_path(conn, :update, recipe), recipe: @invalid_attrs
- assert html_response(conn, 200) =~ "Edit recipe"
- end
-
- @tag logged_in: true
-- test "deletes chosen resource", %{conn: conn} do
-- recipe = Repo.insert! %Recipe{}
-+ test "deletes chosen resource", %{conn: conn, user: user} do
-+ recipe = Repo.insert! %Recipe{user_id: user.id}
- conn = delete conn, recipe_path(conn, :delete, recipe)
- assert redirected_to(conn) == recipe_path(conn, :index)
- refute Repo.get(Recipe, recipe.id)
+@@ -56,30 +56,30 @@ defmodule TvRecipeWeb.RecipeControllerTest do
+-defp create_recipe(%{attrs: attrs} = context) do
++defp create_recipe(%{conn: conn, attrs: attrs} = context) do
+- recipe = fixture(attrs)
++ conn = post conn, Routes.recipe_path(conn, :create), recipe: attrs
++ assert %{id: id} = redirected_params(conn)
++ recipe = Recipes.get_recipe!(id)
+
+ context
+ |> Map.put(:recipe, recipe)
+ end
```
## 数据的完整性
@@ -950,7 +904,9 @@ index 4dbc961..2e2b518 100644
use TvRecipe.ModelCase
- alias TvRecipe.{Recipe}
-+ alias TvRecipe.{Repo, User, Recipe}
++ alias TvRecipe.Repo
++ alias TvRecipe.Users.User
++ alias TvRecipe.Recipes.Recipe
@valid_attrs %{content: "some content", episode: 42, name: "some content", season: 42, title: "some content"}
@invalid_attrs %{}
@@ -964,7 +920,7 @@ index 4dbc961..2e2b518 100644
+ |> Ecto.build_assoc(:recipes)
+ |> Recipe.changeset(@valid_attrs)
+ {:error, changeset} = Repo.insert changeset
-+ assert {:user_id, "does not exist"} in errors_on(changeset)
++ assert %{user_id: ["does not exist"]} = errors_on(changeset)
+ end
+
end
diff --git a/07-recipe/04-recipe-view.md b/07-recipe/04-recipe-view.md
index 69a76dd..75b185e 100644
--- a/07-recipe/04-recipe-view.md
+++ b/07-recipe/04-recipe-view.md
@@ -1,6 +1,6 @@
# 菜谱视图
-我们执行 [`mix phoenix.gen.html` 命令](https://github.com/phoenixframework/phoenix/blob/master/lib/mix/tasks/phoenix.gen.html.ex#L14)时,它会生成如下文件:
+我们执行 [`mix phx.gen.html` 命令](https://github.com/phoenixframework/phoenix/blob/master/lib/mix/tasks/phoenix.gen.html.ex#L14)时,它会生成如下文件:
* a schema in web/models
* a view in web/views
@@ -16,8 +16,8 @@
首先在 `test/views` 目录下新建一个 `recipe_view_test.exs` 文件,然后准备好如下内容:
```elixir
-defmodule TvRecipe.RecipeViewTest do
- use TvRecipe.ConnCase, async: true
+defmodule TvRecipeWeb.RecipeViewTest do
+ use TvRecipeWeb.ConnCase, async: true
# Bring render/3 and render_to_string/3 for testing custom views
import Phoenix.View
@@ -50,14 +50,15 @@ index be4148a..8174c14 100644
# Bring render/3 and render_to_string/3 for testing custom views
import Phoenix.View
-+ alias TvRecipe.Recipe
++ alias TvRecipe.Recipes.Recipe
++ @recipe1 %{id: "1", name: "淘米", title: "侠饭", season: "1", episode: "1", content: "洗掉米表面的淀粉", user_id: "999"}
++ @recipe2 %{id: "2", name: "煮饭", title: "侠饭", season: "1", episode: "1", content: "浸泡", user_id: "888"}
+
+ test "render index.html", %{conn: conn} do
-+ recipes = [%Recipe{id: "1", name: "淘米", title: "侠饭", season: "1", episode: "1", content: "洗掉米表面的淀粉", user_id: "999"},
-+ %Recipe{id: "2", name: "煮饭", title: "侠饭", season: "1", episode: "1", content: "浸泡", user_id: "888"}]
-+ content = render_to_string(TvRecipe.RecipeView, "index.html", conn: conn, recipes: recipes)
++ recipes = [struct(Recipe, @recipe1), struct(Recipe, @recipe2)]
++ content = render_to_string(TvRecipeWeb.RecipeView, "index.html", conn: conn, recipes: recipes)
+ # 页面上包含标题 Listing recipes
-+ assert String.contains?(content, "Listing recipes")
++ assert String.contains?(content, "Listing Recipes")
+ for recipe <- recipes do
+ # 页面上包含菜谱名
+ assert String.contains?(content, recipe.name)
@@ -163,7 +164,7 @@ index a1b75c6..7bd839c 100644
+++ b/test/controllers/user_controller_test.exs
@@ -29,6 +29,7 @@ defmodule TvRecipe.UserControllerTest do
# 注册后自动登录,检查首页是否包含用户名
- conn = get conn, page_path(conn, :index)
+ conn = get conn, Routes.page_path(conn, :index)
assert html_response(conn, 200) =~ Map.get(@valid_attrs, :username)
+ assert html_response(conn, 200) =~ "菜谱"
end
@@ -176,11 +177,11 @@ index b13f370..49240c9 100644
@@ -19,6 +19,7 @@
Get Started
<%= if @current_user do %>
-
<%= link @current_user.username, to: user_path(@conn, :show, @current_user) %>
-+
<%= link "菜谱", to: recipe_path(@conn, :index) %>
-
<%= link "退出", to: session_path(@conn, :delete, @current_user), method: "delete" %>
+
<%= link @current_user.username, to: Routes.user_path(@conn, :show, @current_user) %>
++
<%= link "菜谱", to: Routes.recipe_path(@conn, :index) %>
+
<%= link "退出", to: Routes.session_path(@conn, :delete, @current_user), method: "delete" %>
<% else %>
-
<%= link "登录", to: session_path(@conn, :new) %>
+
<%= link "登录", to: Routes.session_path(@conn, :new) %>
```
运行测试:
diff --git a/07-recipe/05-recipe-tv-url.md b/07-recipe/05-recipe-tv-url.md
index bbbea68..0fb4e57 100644
--- a/07-recipe/05-recipe-tv-url.md
+++ b/07-recipe/05-recipe-tv-url.md
@@ -73,12 +73,12 @@ index 8b093ed..f1ba3f9 100644
--- a/test/models/recipe_test.exs
+++ b/test/models/recipe_test.exs
@@ -60,4 +60,9 @@ defmodule TvRecipe.RecipeTest do
- assert {:user_id, "does not exist"} in errors_on(changeset)
+ assert %{user_id: ["does not exist"]} = errors_on(changeset)
end
+ test "url should be valid" do
+ attrs = Map.put(@valid_attrs, :url, "fjsalfa")
-+ assert {:url, "url 错误"} in errors_on(%Recipe{}, attrs)
++ assert %{url: ["url 错误"]} = errors_on(%Recipe{}, attrs)
+ end
+
end
@@ -92,9 +92,9 @@ mix test
1) test url should be valid (TvRecipe.RecipeTest)
test/models/recipe_test.exs:63
Assertion with in failed
- code: {:url, "url 错误"} in errors_on(%Recipe{}, attrs)
- left: {:url, "url 错误"}
- right: []
+ code: %{url: ["url 错误"]} = errors_on(%Recipe{}, attrs)
+ left: %{url: ["url 错误"]}
+ right: %{}
stacktrace:
test/models/recipe_test.exs:65: (test)
@@ -122,9 +122,11 @@ index 104db50..3b849c8 100644
+
+ defp validate_url(changeset, field, _options \\ []) do
+ validate_change changeset, field, fn _, url ->
-+ case url |> String.to_charlist |> :http_uri.parse do
-+ {:ok, _} -> []
-+ {:error, _} -> [url: "url 错误"]
++ with %{host: _, scheme: scheme} <- :uri_string.parse(url),
++ true <- String.starts_with?(scheme, "http") do
++ []
++ else
++ _ -> [url: "url 错误"]
+ end
+ end
+ end
@@ -141,27 +143,32 @@ diff --git a/test/views/recipe_view_test.exs b/test/views/recipe_view_test.exs
index 8174c14..9695647 100644
--- a/test/views/recipe_view_test.exs
+++ b/test/views/recipe_view_test.exs
+- @recipe1 %{id: "1", name: "淘米", title: "侠饭", season: "1", episode: "1", content: "洗掉米表面的淀粉", user_id: "999"}
+- @recipe2 %{id: "2", name: "煮饭", title: "侠饭", season: "1", episode: "1", content: "浸泡", user_id: "888"}
+# 使用带有 url field 的数据
++ @recipe1 %{id: "1", name: "淘米", title: "侠饭", season: "1", episode: "1", content: "洗掉米表面的淀粉", user_id: "999", url: "http://localhost"}
++ @recipe2 %{id: "2", name: "煮饭", title: "侠饭", season: "1", episode: "1", content: "浸泡", user_id: "888", url: "http://localhost"}
@@ -28,4 +28,23 @@ defmodule TvRecipe.RecipeViewTest do
end
end
+ test "render new.html", %{conn: conn} do
-+ changeset = Recipe.changeset(%Recipe{})
++ changeset = Recipe.changeset(%Recipe{}, %{})
+ content = render_to_string(TvRecipe.RecipeView, "new.html", conn: conn, changeset: changeset)
+ assert String.contains?(content, "url")
+ end
+
+ test "render show.html", %{conn: conn} do
-+ recipe = %Recipe{id: "1", name: "淘米", title: "侠饭", season: "1", episode: "1", content: "洗掉米表面的淀粉", user_id: "999", url: "https://github.com/chenxsan/PhoenixFramework"}
++ recipe = struct(Recipe, @recipe1)
+ content = render_to_string(TvRecipe.RecipeView, "show.html", conn: conn, recipe: recipe)
-+ assert String.contains?(content, recipe.url)
++ assert String.contains?(content, @recipe1.url)
+ end
+
+ test "render edit.html", %{conn: conn} do
-+ recipe = %Recipe{id: "1", name: "淘米", title: "侠饭", season: "1", episode: "1", content: "洗掉米表面的淀粉", user_id: "999", url: "https://github.com/chenxsan/PhoenixFramework"}
-+ changeset = Recipe.changeset(recipe)
++ recipe = struct(Recipe, @recipe1)
++ changeset = Recipe.changeset(%Recipe{}, @recipe1)
+ content = render_to_string(TvRecipe.RecipeView, "edit.html", conn: conn, changeset: changeset, recipe: recipe)
-+ assert String.contains?(content, recipe.url)
++ assert String.contains?(content, @recipe1.url)
+ end
+
end