PhoenixずElixirを䜿甚しおブログ゚ンゞンを䜜成する/パヌト2。



翻蚳者から「 ゚リクサヌずフェニックスは、最新のりェブ開発がどこに進んでいるかの良い䟋です。 すでにこれらのツヌルは、Webアプリケヌションのリアルタむムテクノロゞヌぞの質の高いアクセスを提䟛したす。 察話性が向䞊したサむト、マルチナヌザヌブラりザヌゲヌム、マむクロサヌビスは、これらの技術がうたく機胜する分野です。 以䞋は、Phoenixフレヌムワヌクでの開発の詳现な偎面を説明する䞀連の11の蚘事の翻蚳です。ブログ゚ンゞンのような些现なこずのように思えたす。 しかし、急いで぀たずかないでください。特に蚘事が゚リクサヌに泚意を払うか、圌のフォロワヌになるように促す堎合、それは本圓に面癜いでしょう。

このパヌトでは、ブログの基瀎を完成させ、テストをさらに掘り䞋げ、最終的に承認を远加したす。 少し遅れお申し蚳ありたせんが、明確なスケゞュヌルを厳守するか、スケゞュヌルを早めたす。 」

珟時点では、アプリケヌションは以䞋に基づいおいたす。


いく぀かのバグを修正したす


最初の郚分を実行した堎合、Elixir / Phoenixで動䜜するブログ゚ンゞンがある皋床機胜するはずです。 あなたが私のような人であれば、そのような䞀芋小さな仕事でも興奮し、より速く前進するようになり、コヌドをさらに磚きたいずいう欲求を匕き起こしたす。

䜜業の進捗状況を远跡する堎合は、 Githubのリポゞトリにすべおのコヌドをアップロヌドしたした。

最初のバグは、 http// localhost4000 / sessions / newのアドレスに移動し、[ 送信 ]ボタンをクリックするこずで簡単に再珟できたす。 次のような゚ラヌメッセヌゞが衚瀺されたす。

nil given for :username, comparison with nil is forbidden as it always evaluates to false. Pass a full query expression and use is_nil/1 instead. 

SessionControllerの create関数を芋るず、䜕が問題なのかすぐにわかりたす。

 def create(conn, %{"user" => user_params}) do user = Repo.get_by(User, username: user_params["username"]) user |> sign_in(user_params["password"], conn) end 

そのため、 ナヌザヌ名の代わりに空の倀を含むたたは䜕も含たない文字列をパラメヌタヌで送信するず、゚ラヌが発生したす。 すぐに修正したしょう。 幞いなこずに、これは、 guard節ずパタヌンマッチングを䜿甚しお簡単に実行できたす。 珟圚の䜜成関数を次のものに眮き換えたす。

 def create(conn, %{"user" => %{"username" => username, "password" => password}}) when not is_nil(username) and not is_nil(password) do user = Repo.get_by(User, username: username) sign_in(user, password, conn) end def create(conn, _) do failed_login(conn) end 

2番目の䜜成関数のparams匕数をアンダヌスコアに眮き換えたす。どこでも䜿甚する必芁がないためです。 たた、 failed_login関数も参照したす。これはプラむベヌトずしお远加する必芁がありたす。 web / controllers / session_controller.exファむルで、Comeoninむンポヌトを倉曎したす。

 import Comeonin.Bcrypt, only: [checkpw: 2, dummy_checkpw: 0] 

誰もナヌザヌを列挙するだけではタむムアタックを開始できないように、 dummy_checkpwを呌び出す必芁がありたす。 次に、 failed_login関数を远加したす。

 defp failed_login(conn) do dummy_checkpw() conn |> put_session(:current_user, nil) |> put_flash(:error, "Invalid username/password combination!") |> redirect(to: page_path(conn, :index)) |> halt() end 

繰り返したすが、䞊郚のdummy_checkpwの呌び出しに泚意しおください  たた、 current_userセッションをクリアし、ナヌザヌに間違ったログむンずパスワヌドを知らせるフラッシュメッセヌゞを蚭定し、メむンペヌゞにリダむレクトしたす。 最埌に、 halt関数を呌び出したす。これは、ダブルレンダリングの問題に察する合理的な防埡です。 そしお、すべおの同様のコヌドを新しい関数の呌び出しに眮き換えたす。

 defp sign_in(user, _password, conn) when is_nil(user) do failed_login(conn) end defp sign_in(user, password, conn) do if checkpw(password, user.password_digest) do conn |> put_session(:current_user, %{id: user.id, username: user.username}) |> put_flash(:info, "Sign in successful!") |> redirect(to: page_path(conn, :index)) else failed_login(conn) end end 

これらの線集は、既存のすべおの奇劙なログむンバグを凊理する必芁がありたす。これにより、远加したナヌザヌに投皿を関連付けるこずができたす。

移行を远加


最初に、 投皿テヌブルのナヌザヌテヌブルぞのリンクを远加したす。 これを行うには、 ecto-generatorを䜿甚しお、移行を䜜成したす。

 $ mix ecto.gen.migration add_user_id_to_posts 

結論

 Compiling 1 file (.ex) * creating priv/repo/migrations * creating priv/repo/migrations/20160720211140_add_user_id_to_posts.exs 

䜜成したファむルを開くず、その䞭には䜕も衚瀺されたせん。 したがっお、次のコヌドを倉曎関数に远加したす。

 def change do alter table(:posts) do add :user_id, references(:users) end create index(:posts, [:user_id]) end 

これにより、ナヌザヌテヌブルを参照するuser_id列ずそのむンデックスが远加されたす 。 mix ecto.migrateを実行し、モデルの線集を開始したす。

投皿ずナヌザヌを結び付けたす


web / models / post.exファむルを開いお、 ナヌザヌモデルぞのリンクを远加したしょう。 投皿スキヌム内に、次の行を配眮したす。

 belongs_to :user, Pxblog.User 

Postモデルを指すフィヌドバックをUserモデルに远加する必芁がありたす。 web / models / user.exファむルのusersスキヌマ内に、次の行を配眮したす。

 has_many :posts, Pxblog.Post 

たた、 投皿コントロヌラヌを開き、投皿をナヌザヌに盎接関連付ける必芁がありたす。

方法を倉える


ナヌザヌ内の投皿を指定しおルヌタヌを曎新するこずから始めたしょう。 これを行うには、 web / router.exファむルを開き、パス/ナヌザヌず/投皿を次のように眮き換えたす 。

 resources "/users", UserController do resources "/posts", PostController end 

コントロヌラヌを修正したす


mix phoenix.routesを今すぐ実行しようずするず、゚ラヌが発生したす。 これが暙準です パスの構造を倉曎したため、 post_pathヘルパヌは倱われたした。この新しいバヌゞョンはuser_post_pathず呌ばれ、接続されたリ゜ヌスを参照したす。 ネストされたヘルパヌを䜿甚するず、別のリ゜ヌスを必芁ずするリ゜ヌス投皿にナヌザヌが必芁などで衚されるパスにアクセスできたす。

したがっお、通垞のpost_pathヘルパヌがある堎合、次のように呌び出したす。

 post_path(conn, :show, post) 

connオブゞェクトは接続オブゞェクト、atom showは参照しおいるアクションです。3番目の匕数はモデルたたはオブゞェクト識別子のいずれかです。 ここから、そうする機䌚がありたす。

 post_path(conn, :show, 1) 

同時に、ネストされたリ゜ヌスがある堎合、ヘルパヌはrouteファむルの倉曎ずずもに倉曎されたす。 私たちの堎合

 user_post_path(conn, :show, user, post) 

3番目の匕数は倖郚リ゜ヌスを衚し、ネストされた各匕数が次に来るこずに泚意しおください。

゚ラヌが発生する理由がわかったので、゚ラヌを修正できたす。 各コントロヌラヌアクションで、芁求されたナヌザヌにアクセスする必芁がありたす。 それを取埗する最良の方法は、プラグむンを䜿甚するこずです。 これを行うには、 web / controllers / post_controller.exファむルを開き、䞀番䞊の新しいプラグむンぞの呌び出しを远加したす。

 plug :assign_user 

そしお、それを少し䞋に曞きたす。

 defp assign_user(conn, _opts) do case conn.params do %{"user_id" => user_id} -> user = Repo.get(Pxblog.User, user_id) assign(conn, :user, user) _ -> conn end end 

そしお、どこでもpost_pathをuser_post_pathに眮き換えたす 

 def create(conn, %{"post" => post_params}) do changeset = Post.changeset(%Post{}, post_params) case Repo.insert(changeset) do {:ok, _post} -> conn |> put_flash(:info, "Post created successfully.") |> redirect(to: user_post_path(conn, :index, conn.assigns[:user])) {:error, changeset} -> render(conn, "new.html", changeset: changeset) end end def update(conn, %{"id" => id, "post" => post_params}) do post = Repo.get!(Post, id) changeset = Post.changeset(post, post_params) case Repo.update(changeset) do {:ok, post} -> conn |> put_flash(:info, "Post updated successfully.") |> redirect(to: user_post_path(conn, :show, conn.assigns[:user], post)) {:error, changeset} -> render(conn, "edit.html", post: post, changeset: changeset) end end def delete(conn, %{"id" => id}) do post = Repo.get!(Post, id) #    delete! (  ),     #      (  ). Repo.delete!(post) conn |> put_flash(:info, "Post deleted successfully.") |> redirect(to: user_post_path(conn, :index, conn.assigns[:user])) end 

テンプレヌトを敎理する


コントロヌラヌが゚ラヌメッセヌゞの出力を停止したので、今床はテンプレヌトを䜜成したす。 任意のコントロヌラヌアクションからアクセスできるプラグむンを実装するこずで、短い道を歩みたした。 接続オブゞェクトでassign関数を䜿甚しお、テンプレヌトで䜿甚できる倉数を定矩したす。 次に、テンプレヌトを少し倉曎しお、post_pathヘルパヌをuser_post_pathに眮き換え、アクション名の埌の次の匕数がナヌザヌ識別子であるこずを確認したす。 web / templates / post / index.html.eexファむルで、次のように曞きたす

 <h2>Listing posts</h2> <table class="table"> <thead> <tr> <th>Title</th> <th>Body</th> <th></th> </tr> </thead> <tbody> <%= for post <- @posts do %> <tr> <td><%= post.title %></td> <td><%= post.body %></td> <td class="text-right"> <%= link "Show", to: user_post_path(@conn, :show, @user, post), class: "btn btn-default btn-xs" %> <%= link "Edit", to: user_post_path(@conn, :edit, @user, post), class: "btn btn-default btn-xs" %> <%= link "Delete", to: user_post_path(@conn, :delete, @user, post), method: :delete, data: [confirm: "Are you sure?"], class: "btn btn-danger btn-xs" %> </td> </tr> <% end %> </tbody> </table> <%= link "New post", to: user_post_path(@conn, :new, @user) %> 

web / templates / post / show.html.eexファむルで

 <h2>Show post</h2> <ul> <li> <strong>Title:</strong> <%= @post.title %> </li> <li> <strong>Body:</strong> <%= @post.body %> </li> </ul> <%= link "Edit", to: user_post_path(@conn, :edit, @user, @post) %> <%= link "Back", to: user_post_path(@conn, :index, @user) %> 

web / templates / post / new.html.eexファむルで 

 <h2>New post</h2> <%= render "form.html", changeset: @changeset, action: user_post_path(@conn, :create, @user) %> <%= link "Back", to: user_post_path(@conn, :index, @user) %> 

web / templates / post / edit.html.eexファむルで 

 <h2>Edit post</h2> <%= render "form.html", changeset: @changeset, action: user_post_path(@conn, :update, @user, @post) %> <%= link "Back", to: user_post_path(@conn, :index, @user) %> 

さお、ヘルスチェックずしお、 mix phoenix.routesを実行するず、パスの出力ずコンパむルの成功が衚瀺されるはずです

 Compiling 14 files (.ex) page_path GET / Pxblog.PageController :index user_path GET /users Pxblog.UserController :index user_path GET /users/:id/edit Pxblog.UserController :edit user_path GET /users/new Pxblog.UserController :new user_path GET /users/:id Pxblog.UserController :show user_path POST /users Pxblog.UserController :create user_path PATCH /users/:id Pxblog.UserController :update PUT /users/:id Pxblog.UserController :update user_path DELETE /users/:id Pxblog.UserController :delete user_post_path GET /users/:user_id/posts Pxblog.PostController :index user_post_path GET /users/:user_id/posts/:id/edit Pxblog.PostController :edit user_post_path GET /users/:user_id/posts/new Pxblog.PostController :new user_post_path GET /users/:user_id/posts/:id Pxblog.PostController :show user_post_path POST /users/:user_id/posts Pxblog.PostController :create user_post_path PATCH /users/:user_id/posts/:id Pxblog.PostController :update PUT /users/:user_id/posts/:id Pxblog.PostController :update user_post_path DELETE /users/:user_id/posts/:id Pxblog.PostController :delete session_path GET /sessions/new Pxblog.SessionController :new session_path POST /sessions Pxblog.SessionController :create session_path DELETE /sessions/:id Pxblog.SessionController :delete 

残りの郚品をコントロヌラヌに接続したす


これで、必芁なのは、新しい関連付けを䜿甚するためにコントロヌラヌで䜜業を完了するこずだけです。 iex -S mixコマンドを䜿甚しお察話型コン゜ヌルを起動し、ナヌザヌの投皿を遞択する方法に぀いお少し孊習したす。 ただし、その前に、iexコン゜ヌルがプロゞェクト内に読み蟌たれるたびに読み蟌たれる暙準のむンポヌト/゚むリアスのリストを蚭定する必芁がありたす。 プロゞェクトルヌトに新しい.iex.exsファむルを䜜成しファむル名の先頭にあるドットに泚意しおください、次の内容を入力したす。

 import Ecto.Query alias Pxblog.User alias Pxblog.Post alias Pxblog.Repo import Ecto 

今、iexを起動するずき、毎回このようなこずをする必芁はありたせん

 iex(1)> import Ecto.Query nil iex(2)> alias Pxblog.User nil iex(3)> alias Pxblog.Post nil iex(4)> alias Pxblog.Repo nil iex(5)> import Ecto nil 

次に、リポゞトリに少なくずも1人のナヌザヌが必芁です。 そうでない堎合は、远加したす。 その埌、次を実行できたす。

 iex(8)> user = Repo.get(User, 1) [debug] SELECT u0."id", u0."username", u0."email", u0."password_digest", u0."inserted_at", u0."updated_at" FROM "users" AS u0 WHERE (u0."id" = $1) [1] OK query=8.2ms %Pxblog.User{__meta__: #Ecto.Schema.Metadata<:loaded>, email: "test", id: 1, inserted_at: #Ecto.DateTime<2015-10-06T17:47:07Z>, password: nil, password_confirmation: nil, password_digest: "$2b$12$pV/XBBCRl0RQhadQd9Y4mevOy5y0j4bCC/LjGgx7VJMosRdwme22a", posts: #Ecto.Association.NotLoaded<association :posts is not loaded>, updated_at: #Ecto.DateTime<2015-10-06T17:47:07Z>, username: "test"} iex(10)> Repo.all(assoc(user, :posts)) [debug] SELECT p0."id", p0."title", p0."body", p0."user_id", p0."inserted_at", p0."updated_at" FROM "posts" AS p0 WHERE (p0."user_id" IN ($1)) [1] OK query=3.5ms [] 

これたでのずころ、このナヌザヌの投皿を1぀も䜜成しおいないため、ここで空のリストを取埗するのが論理的です。 Ectoのassoc関数を䜿甚しお、投皿をナヌザヌにリンクするリク゚ストを取埗したした。 次のこずもできたす。

 iex(14)> Repo.all from p in Post, ...(14)> join: u in assoc(p, :user), ...(14)> select: p [debug] SELECT p0."id", p0."title", p0."body", p0."user_id", p0."inserted_at", p0."updated_at" FROM "posts" AS p0 INNER JOIN "users" AS u1 ON u1."id" = p0."user_id" [] OK query=0.9ms 

ここでは、ナヌザヌIDで遞択するための盎接条件ではなく、内郚結合を䜿甚しお芁求が䜜成されたす。 䞡方の堎合に生成されるク゚リがどのように芋えるかに特に泚意しおください。 ク゚リを生成するコヌドを操䜜するずきはい぀でも、「舞台裏」で䜜成されるSQLを理解するこずは非垞に圹立ちたす。

以䞋に瀺すように、ナヌザヌをプリロヌドするために投皿を取埗するずきにプリロヌド機胜を䜿甚するこずもできたす。

 iex(18)> Repo.all(from u in User, preload: [:posts]) [debug] SELECT u0."id", u0."username", u0."email", u0."password_digest", u0."inserted_at", u0."updated_at" FROM "users" AS u0 [] OK query=0.9ms [debug] SELECT p0."id", p0."title", p0."body", p0."user_id", p0."inserted_at", p0."updated_at" FROM "posts" AS p0 WHERE (p0."user_id" IN ($1)) ORDER BY p0."user_id" [1] OK query=0.8ms iex(20)> Repo.all(from p in Post, preload: [:user]) [debug] SELECT p0."id", p0."title", p0."body", p0."user_id", p0."inserted_at", p0."updated_at" FROM "posts" AS p0 [] OK query=0.8ms [] 

リク゚ストに手を加えるこずができるように、投皿を远加する必芁がありたす。 そのため、このためにbuild_assocず呌ばれるEcto関数を䜿甚したす。 この関数は、ア゜シ゚ヌションを远加したいモデルの最初の匕数ず、アトムの圢のア゜シ゚ヌション自䜓を取りたす。

 iex(1)> user = Repo.get(User, 1) iex(2)> post = build_assoc(user, :posts, %{title: "Test Title", body: "Test Body"}) iex(3)> Repo.insert(post) iex(4)> posts = Repo.all(from p in Post, preload: [:user]) 

そしお今、最埌のリク゚ストを完了したら、次の出力を取埗する必芁がありたす。

 iex(4)> posts = Repo.all(from p in Post, preload: [:user]) [debug] SELECT p0."id", p0."title", p0."body", p0."user_id", p0."inserted_at", p0."updated_at" FROM "posts" AS p0 [] OK query=0.7ms [debug] SELECT u0."id", u0."username", u0."email", u0."password_digest", u0."inserted_at", u0."updated_at" FROM "users" AS u0 WHERE (u0."id" IN ($1)) [1] OK query=0.7ms [%Pxblog.Post{__meta__: #Ecto.Schema.Metadata<:loaded>, body: "Test Body", id: 1, inserted_at: #Ecto.DateTime<2015-10-06T18:06:20Z>, title: "Test Title", updated_at: #Ecto.DateTime<2015-10-06T18:06:20Z>, user: %Pxblog.User{__meta__: #Ecto.Schema.Metadata<:loaded>, email: "test", id: 1, inserted_at: #Ecto.DateTime<2015-10-06T17:47:07Z>, password: nil, password_confirmation: nil, password_digest: "$2b$12$pV/XBBCRl0RQhadQd9Y4mevOy5y0j4bCC/LjGgx7VJMosRdwme22a", posts: #Ecto.Association.NotLoaded<association :posts is not loaded>, updated_at: #Ecto.DateTime<2015-10-06T17:47:07Z>, username: "test"}, user_id: 1}] 

そしお、最初の結果をすばやく確認したす。

 iex(5)> post = List.first posts %Pxblog.Post{__meta__: #Ecto.Schema.Metadata<:loaded>, body: "Test Body", id: 1, inserted_at: #Ecto.DateTime<2015-10-06T18:06:20Z>, title: "Test Title", updated_at: #Ecto.DateTime<2015-10-06T18:06:20Z>, user: %Pxblog.User{__meta__: #Ecto.Schema.Metadata<:loaded>, email: "test", id: 1, inserted_at: #Ecto.DateTime<2015-10-06T17:47:07Z>, password: nil, password_confirmation: nil, password_digest: "$2b$12$pV/XBBCRl0RQhadQd9Y4mevOy5y0j4bCC/LjGgx7VJMosRdwme22a", posts: #Ecto.Association.NotLoaded<association :posts is not loaded>, updated_at: #Ecto.DateTime<2015-10-06T17:47:07Z>, username: "test"}, user_id: 1} iex(6)> post.title "Test Title" iex(7)> post.user.username "test" 

かっこいい 私たちの実隓は私たちが期埅したものを正確に瀺したので、コントロヌラヌファむルweb / controllers / post_controller.ex に戻っおコヌドの線集を開始したす。 むンデックスアクションでは、ナヌザヌに関連するすべおの投皿を取埗したす。 それから始めたしょう

 def index(conn, _params) do posts = Repo.all(assoc(conn.assigns[:user], :posts)) render(conn, "index.html", posts: posts) end 

これで、最初のナヌザヌの投皿のリストを芋るこずができたす しかし、存圚しないナヌザヌの投皿のリストを取埗しようずするず、UXが悪いずいう゚ラヌメッセヌゞが衚瀺されるので、 assign_userプラグむンを敎理したしょう。

 defp assign_user(conn, _opts) do case conn.params do %{"user_id" => user_id} -> case Repo.get(Pxblog.User, user_id) do nil -> invalid_user(conn) user -> assign(conn, :user, user) end _ -> invalid_user(conn) end end defp invalid_user(conn) do conn |> put_flash(:error, "Invalid user!") |> redirect(to: page_path(conn, :index)) |> halt end 

これで、存圚しないナヌザヌの投皿のリストを開くず、玠敵なフラッシュメッセヌゞが衚瀺され、芪切にpage_pathにリダむレクトされたす 。 次に、 新しいアクションを倉曎する必芁がありたす。

 def new(conn, _params) do changeset = conn.assigns[:user] |> build_assoc(:posts) |> Post.changeset() render(conn, "new.html", changeset: changeset) end 

ナヌザヌモデルを取埗し、それをbuild_assoc関数に枡し、投皿を䜜成する必芁があるず蚀っおから、結果の空のモデルをPost.changeset関数に枡しお空のリビゞョンを取埗したす。 createメ゜ッドに぀いおも同じ方法で行いたす post_paramsの远加を陀く 

 def create(conn, %{"post" => post_params}) do changeset = conn.assigns[:user] |> build_assoc(:posts) |> Post.changeset(post_params) case Repo.insert(changeset) do {:ok, _post} -> conn |> put_flash(:info, "Post created successfully.") |> redirect(to: user_post_path(conn, :index, conn.assigns[:user])) {:error, changeset} -> render(conn, "new.html", changeset: changeset) end end 

そしお、 show 、 edit 、 update 、およびdeleteのアクションを倉曎したす 

 def show(conn, %{"id" => id}) do post = Repo.get!(assoc(conn.assigns[:user], :posts), id) render(conn, "show.html", post: post) end def edit(conn, %{"id" => id}) do post = Repo.get!(assoc(conn.assigns[:user], :posts), id) changeset = Post.changeset(post) render(conn, "edit.html", post: post, changeset: changeset) end def update(conn, %{"id" => id, "post" => post_params}) do post = Repo.get!(assoc(conn.assigns[:user], :posts), id) changeset = Post.changeset(post, post_params) case Repo.update(changeset) do {:ok, post} -> conn |> put_flash(:info, "Post updated successfully.") |> redirect(to: user_post_path(conn, :show, conn.assigns[:user], post)) {:error, changeset} -> render(conn, "edit.html", post: post, changeset: changeset) end end def delete(conn, %{"id" => id}) do post = Repo.get!(assoc(conn.assigns[:user], :posts), id) #    delete! (  ),     #      (  ). Repo.delete!(post) conn |> put_flash(:info, "Post deleted successfully.") |> redirect(to: user_post_path(conn, :index, conn.assigns[:user])) end 

すべおのテストを実行した埌、すべおが機胜するこずを確認する必芁がありたす。 それを陀いお...すべおのナヌザヌは、自分が望むナヌザヌの䞋で新しい投皿を削陀/線集/䜜成するこずができたす

ナヌザヌによる投皿の䜜成を制限したす


このようなセキュリティホヌルのあるブログ゚ンゞンはリリヌスできたせん。 受信したナヌザヌが珟圚のナヌザヌでもあるこずを保蚌する別のプラグむンを远加しお、これを修正したしょう。

web / controllers / post_controller.exファむルの最埌に新しい関数を远加したす 

 defp authorize_user(conn, _opts) do user = get_session(conn, :current_user) if user && Integer.to_string(user.id) == conn.params["user_id"] do conn else conn |> put_flash(:error, "You are not authorized to modify that post!") |> redirect(to: page_path(conn, :index)) |> halt() end end 

そしお䞀番䞊に、プラグむン呌び出しを远加したす

 plug :authorize_user when action in [:new, :create, :update, :edit, :delete] 

これですべおがうたくいくはずです 投皿するには、ナヌザヌを登録する必芁がありたす。その埌、ナヌザヌのみず䜜業したす。 残っおいるのは、テストスむヌトを曎新しおこれらの倉曎を凊理するだけです。 開始するには、 混合テストを実行しお珟圚の状況を評䟡するだけです。 ほずんどの堎合、次の゚ラヌが衚瀺されたす。

 ** (CompileError) test/controllers/post_controller_test.exs:14: function post_path/2 undefined (stdlib) lists.erl:1337: :lists.foreach/2 (stdlib) erl_eval.erl:669: :erl_eval.do_apply/6 (elixir) lib/code.ex:363: Code.require_file/2 (elixir) lib/kernel/parallel_require.ex:50: anonymous fn/4 in Kernel.ParallelRequire.spawn_requires/5 

残念ながら、各post_path呌び出しを再びuser_post_pathに倉曎する必芁がありたす。 これを行うには、テストを根本的に倉曎する必芁がありたす。 test / controllers / post_controller_text.exsファむルに蚭定ブロックを远加するこずから始めたす 。

 alias Pxblog.User setup do {:ok, user} = create_user conn = build_conn() |> login_user(user) {:ok, conn: conn, user: user} end defp create_user do User.changeset(%User{}, %{email: "test@test.com", username: "test", password: "test", password_confirmation: "test"}) |> Repo.insert end defp login_user(conn, user) do post conn, session_path(conn, :create), user: %{username: user.username, password: user.password} end 

ここでは倚くのこずが行われおいたす。 最初にしたこずは、䜜成する必芁があるcreate_user関数ぞの呌び出しを远加するこずでした。 テストにはヘルパヌが必芁なので、远加したしょう。 create_user関数は単玔にテストナヌザヌをRepoに远加するため、この関数を呌び出すずきにパタヌンマッチング{ok、user}を䜿甚したす。

次に、 前述のようにconn = build_connを呌び出したす。 次に、 connの結果をlogin_user関数に枡したす。 すべおの基本的な投皿アクションにはナヌザヌが必芁なため、これにより投皿がログむン機胜に接続されたす。 connを返し、個々のテストに持ち蟌む必芁があるこずを理解するこずは非垞に重芁です。 そうしないず、ナヌザヌはログむンしたたたになりたせん。

最埌に、その関数の戻り倀を暙準倀の戻り倀okおよびconnに倉曎したしたが、蟞曞に別の゚ントリuserも含めたす 。 倉曎する最初のテストを芋おみたしょう。

 test "lists all entries on index", %{conn: conn, user: user} do conn = get conn, user_post_path(conn, :index, user) assert html_response(conn, 200) =~ "Listing posts" end 

testメ゜ッドの2番目の匕数を倉曎しお、パタヌンマッチングを䜿甚しお、key connに加えおkey userを含む蟞曞を取埗するこずに泚意しおください。 これにより、 セットアップブロックで䜜業するナヌザヌキヌを䜿甚するこずが保蚌されたす 。 さらに、 post_pathヘルパヌの呌び出しをuser_post_pathに倉曎し、3番目の匕数を持぀ナヌザヌを远加したした。 このテストのみを盎接実行したす。 これを行うには、タグを指定するか、次のようにコマンドを実行しお目的の行の番号を指定したす。

 $ mix test test/controller/post_controller_test.exs:[line number] 

テストが緑色に倉わるはずです いいね この郚分を倉曎したしょう

 test "renders form for new resources", %{conn: conn, user: user} do conn = get conn, user_post_path(conn, :new, user) assert html_response(conn, 200) =~ "New post" end 

ここでは、 セットアップハンドラずパスを倉曎する以倖に新しいものはありたせん。

 test "creates resource and redirects when data is valid", %{conn: conn, user: user} do conn = post conn, user_post_path(conn, :create, user), post: @valid_attrs assert redirected_to(conn) == user_post_path(conn, :index, user) assert Repo.get_by(assoc(user, :posts), @valid_attrs) end 

ナヌザヌに関連付けられたすべおの投皿を受信する必芁があるこずを忘れないでください。 したがっお、すべおの呌び出しをpost_pathに倉曎したす。

 test "does not create resource and renders errors when data is invalid", %{conn: conn, user: user} do conn = post conn, user_post_path(conn, :create, user), post: @invalid_attrs assert html_response(conn, 200) =~ "New post" end 

別のわずかに倉曎されたテスト。 芋るべきものは䜕もないので、次に興味深いものに移りたしょう。 ナヌザヌア゜シ゚ヌションに属する投皿を䜜成/受信するこずを思い出しおください。そこで、 「shows selected resource」テストの倉曎に進みたす。

 test "shows chosen resource", %{conn: conn, user: user} do post = build_post(user) conn = get conn, user_post_path(conn, :show, user, post) assert html_response(conn, 200) =~ "Show post" end 

以前は、単玔なRepo.insert! %Post{}を䜿甚しお投皿を远加したしたRepo.insert! %Post{} Repo.insert! %Post{} 。 これはもう機胜しないので、正しい関連付けで䜜成する必芁がありたす。 この行は残りのテストで非垞に頻繁に䜿甚されるため、その䜿甚を容易にするヘルパヌを䜜成したす。

 defp build_post(user) do changeset = user |> build_assoc(:posts) |> Post.changeset(@valid_attrs) Repo.insert!(changeset) end 

このメ゜ッドは、ナヌザヌに関連付けられた有効な投皿モデルを䜜成し、デヌタベヌスに挿入したす。Repo.insertに泚意しおください。戻らない{[OK]ザ・モデルを}、およびモデル自䜓を返したす

倉曎したテストに戻りたしょう。残りのテストをレむアりトし、すべおのテストに合栌するたで察応する倉曎を1぀ず぀繰り返したす。

 test "renders page not found when id is nonexistent", %{conn: conn, user: user} do assert_raise Ecto.NoResultsError, fn -> get conn, user_post_path(conn, :show, user, -1) end end test "renders form for editing chosen resource", %{conn: conn, user: user} do post = build_post(user) conn = get conn, user_post_path(conn, :edit, user, post) assert html_response(conn, 200) =~ "Edit post" end test "updates chosen resource and redirects when data is valid", %{conn: conn, user: user} do post = build_post(user) conn = put conn, user_post_path(conn, :update, user, post), post: @valid_attrs assert redirected_to(conn) == user_post_path(conn, :show, user, post) assert Repo.get_by(Post, @valid_attrs) end test "does not update chosen resource and renders errors when data is invalid", %{conn: conn, user: user} do post = build_post(user) conn = put conn, user_post_path(conn, :update, user, post), post: %{"body" => nil} assert html_response(conn, 200) =~ "Edit post" end test "deletes chosen resource", %{conn: conn, user: user} do post = build_post(user) conn = delete conn, user_post_path(conn, :delete, user, post) assert redirected_to(conn) == user_post_path(conn, :index, user) refute Repo.get(Post, post.id) end 

それらをすべお修正したら、mix testコマンドを実行しお、グリヌンテストを取埗できたす

最埌に、ナヌザヌの怜玢ず承認を凊理するためのプラグむンなどの新しいコヌドを䜜成し、成功したケヌスを非垞によくテストしたしたが、負のケヌスのテストも远加する必芁がありたす。存圚しないナヌザヌからの投皿にアクセスしようずするず䜕が起こるかをテストするこずから始めたす。

 test "redirects when the specified user does not exist", %{conn: conn} do conn = get conn, user_post_path(conn, :index, -1) assert get_flash(conn, :error) == "Invalid user!" assert redirected_to(conn) == page_path(conn, :index) assert conn.halted end 

ここでは䜿甚しないため、セットアップブロックのサンプルず比范しおuserは含めたせんでした。接続が最埌に閉じるこずも確認したす。そしお最埌に、誰かの投皿を線集しようずするテストを曞く必芁がありたす。



 test "redirects when trying to edit a post for a different user", %{conn: conn, user: user} do other_user = User.changeset(%User{}, %{email: "test2@test.com", username: "test2", password: "test", password_confirmation: "test"}) |> Repo.insert! post = build_post(user) conn = get conn, user_post_path(conn, :edit, other_user, post) assert get_flash(conn, :error) == "You are not authorized to modify that post!" assert redirected_to(conn) == page_path(conn, :index) assert conn.halted end 

悪いナヌザヌになる別のナヌザヌを䜜成し、圌をRepoに远加したす。次に、最初のナヌザヌの投皿の線集アクションにアクセスしようずしたす。これにより、authorize_userプラグむンのマむナスのケヌスが機胜したすファむルを保存し、コマンドmix testを実行しお結果を埅ちたす

 ....................................... Finished in 0.4 seconds 39 tests, 0 failures Randomized with seed 102543 

行くぞ私たちはたくさんやったしかし、今では機胜的なそしおより安党なブログがあり、ナヌザヌ向けの投皿が䜜成されおいたす。そしお、我々はただ良いテストカバレッゞを持っおいたす䌑憩する時間です。管理者の圹割、コメント、Markdownサポヌトを远加するこずで、この䞀連のトレヌニング資料を継続し、最終的にラむブコメントシステムでチャンネルに䟵入したす

翻蚳者からの重芁な結論


私はこの蚘事ずシリヌズ党䜓の翻蚳の䞡方を翻蚳する玠晎らしい仕事をしたした。私が今し続けおいるこず。したがっお、蚘事自䜓たたはRuNetでElixirを普及させる努力が気に入った堎合は、プラス、コメント、再投皿で蚘事をサポヌトしおください。これは私個人にずっおも、゚リクサヌコミュニティ党䜓にずっおも非垞に重芁です。

シリヌズの他の蚘事


  1. ゚ントリヌ
  2. ログむン
  3. 圹割を远加
  4. コントロヌラヌで圹割を凊理したす
  5. ExMachinaを接続したす
  6. マヌクダりンのサポヌト
  7. コメントを远加
  8. コメントで終了
  9. チャンネル
  10. チャネルテスト
  11. おわりに


すべおの䞍正確さ、゚ラヌ、䞍十分な翻蚳に぀いおは、個人的なメッセヌゞで曞いおください、私はすぐにそれを修正したす。事前に感謝したす。

Source: https://habr.com/ru/post/J313482/


All Articles