我們的菜譜現(xiàn)在有對應(yīng)的節(jié)目名、哪一季哪一集,如果能直接附上視頻的鏈接,就更完美了。
我們需要一個數(shù)據(jù)庫遷移(migration)文件:
$ mix ecto.gen.migration add_url_to_recipe
* creating priv/repo/migrations
* creating priv/repo/migrations/20170211030550_add_url_to_recipe.exs
修改新建的遷移文件:
diff --git a/priv/repo/migrations/20170211030550_add_url_to_recipe.exs b/priv/repo/migrations/20170211030550_add_url_to_recipe.exs
index 01e5f17..f0918c6 100644
--- a/priv/repo/migrations/20170211030550_add_url_to_recipe.exs
+++ b/priv/repo/migrations/20170211030550_add_url_to_recipe.exs
@@ -2,6 +2,8 @@ defmodule TvRecipe.Repo.Migrations.AddUrlToRecipe do
use Ecto.Migration
def change do
-
+ alter table(:recipes) do
+ add :url, :string
+ end
end
end
接著將 :url
加入 schema 中:
diff --git a/web/models/recipe.ex b/web/models/recipe.ex
index 230f290..104db50 100644
--- a/web/models/recipe.ex
+++ b/web/models/recipe.ex
@@ -6,6 +6,7 @@ defmodule TvRecipe.Recipe do
field :title, :string
field :season, :integer, default: 1
field :episode, :integer, default: 1
+ field :url, :string
field :content, :string
belongs_to :user, TvRecipe.User
@@ -17,7 +18,7 @@ defmodule TvRecipe.Recipe do
"""
def changeset(struct, params \\ %{}) do
struct
- |> cast(params, [:name, :title, :season, :episode, :content])
+ |> cast(params, [:name, :title, :season, :episode, :content, :url])
|> 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ù)字")
最后,執(zhí)行 mix ecto.migrate
:
$ mix ecto.migrate
Compiling 13 files (.ex)
11:53:37.646 [info] == Running TvRecipe.Repo.Migrations.AddUrlToRecipe.change/0 forward
11:53:37.646 [info] alter table recipes
11:53:37.676 [info] == Migrated in 0.0s
接著新增一個測試,我們需要驗證 url 的有效性:
diff --git a/test/models/recipe_test.exs b/test/models/recipe_test.exs
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"]} = errors_on(changeset)
end
+ test "url should be valid" do
+ attrs = Map.put(@valid_attrs, :url, "fjsalfa")
+ assert %{url: ["url 錯誤"]} = errors_on(%Recipe{}, attrs)
+ end
+
end
運行測試:
mix test
...............................
1) test url should be valid (TvRecipe.RecipeTest)
test/models/recipe_test.exs:63
Assertion with in failed
code: %{url: ["url 錯誤"]} = errors_on(%Recipe{}, attrs)
left: %{url: ["url 錯誤"]}
right: %{}
stacktrace:
test/models/recipe_test.exs:65: (test)
...........................
Finished in 1.0 seconds
59 tests, 1 failure
那么我們要如何在 recipe.ex
文件中驗證 url 的有效性?
我們可以考慮用正則表達(dá)式配合 validate_format
,但有個更好的辦法,是直接引用 Erlang 的方法:
diff --git a/web/models/recipe.ex b/web/models/recipe.ex
index 104db50..3b849c8 100644
--- a/web/models/recipe.ex
+++ b/web/models/recipe.ex
@@ -22,6 +22,16 @@ defmodule TvRecipe.Recipe do
|> 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ù)字")
+ |> validate_url(:url)
|> foreign_key_constraint(:user_id)
end
+
+ defp validate_url(changeset, field, _options \\ []) do
+ validate_change changeset, field, fn _, url ->
+ with %{host: _, scheme: scheme} <- :uri_string.parse(url),
+ true <- String.starts_with?(scheme, "http") do
+ []
+ else
+ _ -> [url: "url 錯誤"]
+ end
+ end
+ end
end
我們在 recipe.ex
文件中新增了一個 validate_url
私有方法,并調(diào)用 Ecto 提供的 validate_change 函數(shù)來驗證屬性是否有效。http_uri 是 Erlang 的模塊,在 Elixir 中,我們能夠以 :http_uri
的形式調(diào)用。
我們所有的 recipe 模板都需要做調(diào)整 - 此時,我想你可能已經(jīng)意識到測試驅(qū)動的好處了,如果我們給各個模板添加過測試,那么有新特性加入時,我們先在測試中體現(xiàn)我們的目的,然后運行測試,就知道需要修改哪些文件來達(dá)到我們的目的。
參照前一節(jié)的代碼,我們來進一步完善 RecipeViewTest
模塊的代碼:
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 的數(shù)據(jù)
+ @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{}, %{})
+ 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 = struct(Recipe, @recipe1)
+ content = render_to_string(TvRecipe.RecipeView, "show.html", conn: conn, recipe: recipe)
+ assert String.contains?(content, @recipe1.url)
+ end
+
+ test "render edit.html", %{conn: conn} do
+ 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, @recipe1.url)
+ end
+
end
然后運行測試:
mix test
Compiling 1 file (.ex)
...
1) test render new.html (TvRecipe.RecipeViewTest)
test/views/recipe_view_test.exs:31
Expected truthy, got false
code: String.contains?(content, "url")
stacktrace:
test/views/recipe_view_test.exs:34: (test)
2) test render show.html (TvRecipe.RecipeViewTest)
test/views/recipe_view_test.exs:37
Expected truthy, got false
code: String.contains?(content, recipe.url())
stacktrace:
test/views/recipe_view_test.exs:40: (test)
.
3) test render edit.html (TvRecipe.RecipeViewTest)
test/views/recipe_view_test.exs:43
Expected truthy, got false
code: String.contains?(content, recipe.url())
stacktrace:
test/views/recipe_view_test.exs:47: (test)
.......................................................
Finished in 0.9 seconds
62 tests, 3 failures
根據(jù)測試結(jié)果,我們修改文件:
diff --git a/web/templates/recipe/form.html.eex b/web/templates/recipe/form.html.eex
index 3bf90ff..ab12be3 100644
--- a/web/templates/recipe/form.html.eex
+++ b/web/templates/recipe/form.html.eex
@@ -30,6 +30,12 @@
</div>
<div class="form-group">
+ <%= label f, :url, class: "control-label" %>
+ <%= text_input f, :url, class: "form-control" %>
+ <%= error_tag f, :url %>
+ </div>
+
+ <div class="form-group">
<%= label f, :content, class: "control-label" %>
<%= textarea f, :content, class: "form-control" %>
<%= error_tag f, :content %>
diff --git a/web/templates/recipe/show.html.eex b/web/templates/recipe/show.html.eex
index 3ef437d..f4ea463 100644
--- a/web/templates/recipe/show.html.eex
+++ b/web/templates/recipe/show.html.eex
@@ -23,6 +23,11 @@
</li>
<li>
+ <strong>Url:</strong>
+ <%= @recipe.url %>
+ </li>
+
+ <li>
<strong>Content:</strong>
<%= @recipe.content %>
</li>
最后再運行一次測試:
mix test
Compiling 1 file (.ex)
..............................................................
Finished in 0.9 seconds
62 tests, 0 failures
測試全部通過。
更多建議: