Phoenix Recipe 屬性開發(fā)

2023-12-18 14:56 更新

在開發(fā)用戶時,我們曾經(jīng)分章節(jié)完成各個屬性。但這里不再細(xì)分。

我們來看下 mix phx.gen.html 命令生成的 recipe_test.exs 文件內(nèi)容:

defmodule TvRecipe.RecipeTest do
  use TvRecipe.ModelCase

  alias TvRecipe.Recipe

  @valid_attrs %{content: "some content", episode: 42, name: "some content", season: 42, title: "some content"}
  @invalid_attrs %{}

  test "changeset with valid attributes" do
    changeset = Recipe.changeset(%Recipe{}, @valid_attrs)
    assert changeset.valid?
  end

  test "changeset with invalid attributes" do
    changeset = Recipe.changeset(%Recipe{}, @invalid_attrs)
    refute changeset.valid?
  end
end

很顯然,默認(rèn)生成的 @valid_attrs 是無效的,因為少了一個 user_id。

但我們先處理其它屬性,因為比較簡單。

我們先增加測試:

diff --git a/test/tv_recipe/recipes/recipe_test.exs b/test/tv_recipe/recipes/recipe_test.exs
index a974aad..27f02ea 100644
--- 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?
   end
+
+  test "name is required" do
+    attrs = %{@valid_attrs | name: ""}
+    assert %{name: ["請?zhí)顚?]} = errors_on(%Recipe{}, attrs)
+  end
+
+  test "title is required" do
+    attrs = %{@valid_attrs | title: ""}
+    assert %{title: ["請?zhí)顚?]} = errors_on(%Recipe{}, attrs)
+  end
+
+  test "season is required" do
+    attrs = %{@valid_attrs | season: nil}
+    assert %{season: ["請?zhí)顚?]} = errors_on(%Recipe{}, attrs)
+  end
+
+  test "episode is required" do
+    attrs = %{@valid_attrs | episode: nil}
+    assert %{episode: ["請?zhí)顚?]} = errors_on(%Recipe{}, attrs)
+  end
+
+  test "season should greater than 0" do
+    attrs = %{@valid_attrs | season: 0}
+    assert %{season: ["請輸入大于 0 的數(shù)字"]} = errors_on(%Recipe{}, attrs)
+  end
+
+  test "episode should greater than 0" do
+    attrs = %{@valid_attrs | episode: 0}
+    assert %{episode: ["請輸入大于 0 的數(shù)字"]} = errors_on(%Recipe{}, attrs)
+  end
+
+  test "content is required" do
+    attrs = %{@valid_attrs | content: ""}
+    assert %{content: ["請?zhí)顚?]} = errors_on(%Recipe{}, attrs)
+  end
 end

然后修改 recipe.ex 文件,自定義驗證消息:

diff --git a/web/models/recipe.ex b/web/models/recipe.ex
index 946d45c..8d34ed2 100644
--- a/web/models/recipe.ex
+++ b/web/models/recipe.ex
@@ -18,6 +18,6 @@ defmodule TvRecipe.Recipe do
   def changeset(struct, params \\ %{}) do
     struct
     |> cast(params, [:name, :title, :season, :episode, :content])
-    |> validate_required([:name, :title, :season, :episode, :content])
+    |> validate_required([:name, :title, :season, :episode, :content], message: "請?zhí)顚?)
+    |> validate_number(:season, greater_than: 0, message: "請輸入大于 0 的數(shù)字")
+    |> validate_number(:episode, greater_than: 0, message: "請輸入大于 0 的數(shù)字")
   end
 end

user_id

user_id 有兩條規(guī)則:

  1. user_id 必填
  2. user_id 對應(yīng)著 users 表中用戶的 id,該用戶在表中必須存在

必填

我們先處理 user_id 必填的規(guī)則,補(bǔ)充一個測試,如下:

diff --git a/test/tv_recipe/recipes/recipe_test.exs b/test/tv_recipe/recipes/recipe_test.exs
index 27f02ea..3a9630b 100644
--- 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..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}
   @invalid_attrs %{}

   test "changeset with valid attributes" do
@@ -40,4 +40,9 @@ defmodule TvRecipe.RecipeTest do
     attrs = %{@valid_attrs | content: ""}
     assert %{content: ["請?zhí)顚?]} = errors_on(%Recipe{}, attrs)
   end
+
+  test "user_id is required" do
+    attrs = %{@valid_attrs | user_id: nil}
+    assert %{user_id: ["請?zhí)顚?]} = errors_on(%Recipe{}, attrs)
+  end
 end

然后修改 recipe.ex 文件:

diff --git a/web/models/recipe.ex b/web/models/recipe.ex
index 8d34ed2..0520582 100644
--- a/web/models/recipe.ex
+++ b/web/models/recipe.ex
@@ -17,7 +17,7 @@ defmodule TvRecipe.Recipe do
   """
   def changeset(struct, params \\ %{}) do
     struct
-    |> cast(params, [:name, :title, :season, :episode, :content])
-    |> validate_required([:name, :title, :season, :episode, :content], message: "請?zhí)顚?)
+    |> cast(params, [:name, :title, :season, :episode, :content, :user_id])
+    |> validate_required([:name, :title, :season, :episode, :content, :user_id], message: "請?zhí)顚?)
   end
 end

運行新增的測試:

$ mix test test/tv_recipe/recipes/recipe_test.exs:54
Including tags: [line: "54"]
Excluding tags: [:test]

.

Finished in 0.1 seconds
10 tests, 0 failures, 9 skipped

注意,我們只測試前面新增的測試,:54 表示執(zhí)行該文件中第 54 行開始的 test 塊。

user_id 所指向的用戶應(yīng)存在

我們在 recipe_test.exs 文件中再增加一個測試,確保 user_id 所指的用戶存在:

diff --git a/test/tv_recipe/recipes/recipe_test.exs b/test/tv_recipe/recipes/recipe_test.exs
index 3a9630b..2e1191c 100644
--- 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
+  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: ["請?zhí)顚?]} = 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: ["用戶不存在"]} = errors_on(changeset)
+  end
 end

運行新增的測試:

$ mix test test/tv_recipe/recipes/recipe_test.exs:59
Compiling 13 files (.ex)
Including tags: [line: "59"]
Excluding tags: [:test]



  1) test user_id should exist in users table (TvRecipe.RecipeTest)
     test/tv_recipe/recipes/recipe_test.exs:59
     ** (Ecto.ConstraintError) constraint error when attempting to insert struct:

         * foreign_key: recipes_user_id_fkey

     If you would like to convert this constraint into an error, please
     call foreign_key_constraint/3 in your changeset and define the proper
     constraint name. The changeset has not defined any constraint.

     stacktrace:
       (ecto) lib/ecto/repo/schema.ex:493: anonymous fn/4 in Ecto.Repo.Schema.constraints_to_errors/3
       (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
       (ecto) lib/ecto/repo/schema.ex:684: anonymous fn/3 in Ecto.Repo.Schema.wrap_in_transaction/6
       (ecto) lib/ecto/adapters/sql.ex:615: anonymous fn/3 in Ecto.Adapters.SQL.do_transaction/3
       (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/tv_recipe/recipes/recipe_test.exs:60: (test)



Finished in 0.2 seconds
11 tests, 1 failure, 10 skipped

測試失敗,但 Phoenix 給了 foreign_key_constraint 的提示:

diff --git a/web/models/recipe.ex b/web/models/recipe.ex
index 0520582..a0b42fd 100644
--- a/web/models/recipe.ex
+++ b/web/models/recipe.ex
@@ -19,5 +19,6 @@ defmodule TvRecipe.Recipe do
     struct
     |> cast(params, [:name, :title, :season, :episode, :content, :user_id])
     |> validate_required([:name, :title, :season, :episode, :content, :user_id], message: "請?zhí)顚?)
+    |> foreign_key_constraint(:user_id, message: "用戶不存在")
   end
 end

再次運行測試:

$ mix test test/tv_recipe/recipes/recipe_test.exs:59
Compiling 13 files (.ex)
Including tags: [line: "59"]
Excluding tags: [:test]

.

Finished in 0.1 seconds
11 tests, 0 failures, 10 skipped

測試通過。

但我們運行所有測試的話:

$ mix test
..........................................

  1) test creates resource and redirects when data is valid (TvRecipe.RecipeControllerTest)
     test/controllers/recipe_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/recipe_controller_test.exs:20: (test)



  2) test updates chosen resource and redirects when data is valid (TvRecipe.RecipeControllerTest)
     test/controllers/recipe_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/recipe_controller_test.exs:50: (test)

..........

Finished in 0.6 seconds
56 tests, 2 failures

recipe_controller_test.exs 文件中出現(xiàn)兩個錯誤 - 不過我們留給下一章處理。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號