PhoenixとElixirを使用してブログエンジンを作成する/パート3.ロールを追加する





翻訳者から:「 エリクサーとフェニックスは、最新のウェブ開発がどこに進んでいるかの良い例です。 すでにこれらのツールは、Webアプリケーションのリアルタイムテクノロジーへの質の高いアクセスを提供します。 対話性が向上したサイト、マルチユーザーブラウザーゲーム、マイクロサービスは、これらの技術がうまく機能する分野です。 以下は、Phoenixフレームワークでの開発の詳細な側面を説明する一連の11の記事の翻訳です。ブログエンジンのような些細なことのように思えます。 しかし、急いでつまずかないでください。特に記事がエリクサーに注意を払うか、彼のフォロワーになるように促す場合、それは本当に面白いでしょう。



このパートでは、ロールのサポートを追加し、アクセス権の差別化を開始します。 このシリーズの記事の重要なポイントは、テストに多くの注意が払われているということです。



最後まで読んで、Wunsh.ruに登録する必要がある理由と、非常に役立つ賞品を獲得する方法を確認してください



現時点では、アプリケーションは以下に基づいています。





私たちが泊まった場所



投稿とユーザーの関連付けを終了し、投稿者ではないユーザーの投稿へのアクセスを制限するプロセスを開始したという事実について、前回あなたと別れました。 また、一部の悪役がすべてのユーザーまたはすべての投稿を削除するイベントの発生を待たずに、ユーザーがルールを遵守するようにします。



この問題を解決するために、かなり標準的なアプローチを使用します:ロールを作成します。



ロール作成



ターミナルで次のコマンドを実行することから始めましょう。



$ mix phoenix.gen.model Role roles name:string admin:boolean
      
      





同様のものを出力するはずです:



 * creating web/models/role.ex * creating test/models/role_test.exs * creating priv/repo/migrations/20160721151158_create_role.exs Remember to update your repository by running migrations: $ mix ecto.migrate
      
      





スクリプトのアドバイスを使用して、すぐにmix ecto.migrate



起動することをお勧めします。 データベースが適切に構成されているという仮定に基づいて、同様の結論が得られるはずです。



 Compiling 21 files (.ex) Generated pxblog app 11:12:04.736 [info] == Running Pxblog.Repo.Migrations.CreateRole.change/0 forward 11:12:04.736 [info] create table roles 11:12:04.742 [info] == Migrated in 0.0s
      
      





また、テストを実行して、新しいモデルを追加しても問題が発生しないことを確認します。 すべてが緑の場合は、次に進み、ロールをユーザーに関連付けます。



ロールとユーザーの関係を追加する



この機能を実現するために私が従った主なアイデアは、各ユーザーは1つのロールのみを持つことができ、各ロールは一度に複数のユーザーに属するということです。 これを行うには、以下で説明するweb/models/user.ex



web/models/user.ex



ます。



「users」スキーマセクションに、次の行を追加します。



 belongs_to :role, Pxblog.Role
      
      





この場合、外部キーrole_idusersテーブルに配置します。つまり、ユーザーはロールに「属している」と言います。 また、 web/models/role.ex



を開き、次の行をスキームの「roles」セクションに追加します。



 has_many :users, Pxblog.User
      
      





その後、テストを再度実行しますが、今回は大量のエラーが発生します。 Ectoに、ユーザーテーブルはロールテーブルと関係がありますが、データベース内のどこにも識別されていないことを伝えました。 したがって、ロールへのリンクをrole_idフィールドに保存するために、ユーザーテーブルを変更する必要があります。 次のコマンドを呼び出します。



 $ mix ecto.gen.migration add_role_id_to_users
      
      





結論:



 Compiling 5 files (.ex) * creating priv/repo/migrations * creating priv/repo/migrations/20160721184919_add_role_id_to_users.exs
      
      





新しく作成した移行ファイルを開きましょう。 デフォルトでは、次のようになります。



 defmodule Pxblog.Repo.Migrations.AddRoleIdToUsers do use Ecto.Migration def change do end end
      
      





調整が必要です。 usersテーブルを変更することから始めましょう。 この方法でロールへのリンクを追加します。



 alter table(:users) do add :role_id, references(:roles) end
      
      





また、 role_idフィールドにインデックスを追加する必要があります。



 create index(:users, [:role_id])
      
      





最後に、 mix ecto.migrate



再度実行します。 移行が成功する必要があります! ここでテストを実行すると、すべてが再び緑色になります!



残念ながら、私たちのテストは完璧ではありません。 まず、 Post / Userモデルでは変更しませんでした。 したがって、たとえば、ユーザーが投稿に対して明確に定義されていることを確認することはできません。 同様に、ロールなしでユーザーを作成することはできません。 次のように、 web/models/user.ex



変更セット関数を変更します(追加に注意してください:role_idは2つの場所にあります):



 def changeset(struct, params \\ %{}) do struct |> cast(params, [:username, :email, :password, :password_confirmation, :role_id]) |> validate_required([:username, :email, :password, :password_confirmation, :role_id]) |> hash_password end
      
      





テストのヘルパーを作成する



テストを実行した結果、多数のエラーが発生する可能性がありますが、これは正常です! それらを整理するには、多くの作業が必要です。 そして、何度も何度も同じコードを記述しないようにするためのテストヘルパーを追加することから始めます。 新しいファイルtest/support/test_helper.ex



を作成し、次のコードを入力します。



 defmodule Pxblog.TestHelper do alias Pxblog.Repo alias Pxblog.User alias Pxblog.Role alias Pxblog.Post import Ecto, only: [build_assoc: 2] def create_role(%{name: name, admin: admin}) do Role.changeset(%Role{}, %{name: name, admin: admin}) |> Repo.insert end def create_user(role, %{email: email, username: username, password: password, password_confirmation: password_confirmation}) do role |> build_assoc(:users) |> User.changeset(%{email: email, username: username, password: password, password_confirmation: password_confirmation}) |> Repo.insert end def create_post(user, %{title: title, body: body}) do user |> build_assoc(:posts) |> Post.changeset(%{title: title, body: body}) |> Repo.insert end end
      
      





テストをさらに編集する前に、このファイルの機能について説明しましょう。 最初に注意すべきことは、それをどこに置くかです。 つまり、 test / supportディレクトリに、一般的なテストで使用できるようにするために任意のモジュールを配置することもできます。 各テストファイルからこのヘルパーを参照する必要がありますが、参照する必要があります。



そのため、最初にRepoUserRole、およびPostモジュールのエイリアスを指定して、それらを呼び出すための構文を短縮します。 次に、 Ectoをインポートしてbuild_assoc関数にアクセスし、関連付けを作成します。



create_role関数は、ロール名と管理者フラグを含む辞書を受け取ることが期待されています。 Repo.insert



ではRepo.insert



関数を使用したため、追加が成功すると標準応答{:ok, model}



を取得します。 言い換えれば、これは単なる役割の改訂の挿入です。



入力ロールからチェーンを下に移動し始めます。入力ロールはユーザーモデルを作成するために渡します(定義したのは、 ユーザーを関連付けとして)、これに基づいて、上記のパラメーターを使用してユーザーリビジョンを作成します 。 最終結果はRepo.insert()



関数に渡され、完了です!



説明するのは難しいですが、非常に読みやすく、非常に理解しやすいコードを扱っています。 ロールを取得し、それに関連付けられたユーザーを作成し、データベースに追加する準備をしてから、直接追加します!



create_post関数は、ユーザーとロールの代わりに投稿とユーザーで作業することを除いて、同様のことを行います!



テストを修正します



ファイルtest/models/user_test.exs



編集することから始めましょう。 最初に、モジュール定義の最上部にalias Pxblog.TestHelper



を追加する必要があります。これにより、少し前に作成した便利なヘルパーを使用できます。 次に、テストの前にセットアップブロックを作成して、ロールを再利用します。



 setup do {:ok, role} = TestHelper.create_role(%{name: "user", admin: false}) {:ok, role: role} end
      
      





そして、パターンマッチングを使用した最初のテストでは、 セットアップブロックから役割を取得します 。 もう少し時間を節約して、ロールとともに有効な属性を取得するヘルパー関数を作成しましょう。



 defp valid_attrs(role) do Map.put(@valid_attrs, :role_id, role.id) end test "changeset with valid attributes", %{role: role} do changeset = User.changeset(%User{}, valid_attrs(role)) assert changeset.valid? end
      
      





まとめると。 セットアップブロックから取得したロールキーをサンプルにマップし、 valid_attrsキーを変更して、ヘルパーに有効なロールを含めます。 このテストを変更して再度実行すると、すぐにファイルtest/models/user_test.exs



の緑色の状態に戻ります。



ここで、ファイルtest/controllers/user_controller_test.exs



開きます。 それからテストに合格するには、同じレッスンを使用します。 一番上で、 alias Pxblog.Role



追加し、次にalias Pxblog.Role



追加します。 その後、ロールが作成されconnオブジェクトが返されるセットアップブロックを配置します



 setup do {:ok, user_role} = TestHelper.create_role(%{name: "user", admin: false}) {:ok, admin_role} = TestHelper.create_role(%{name: "admin", admin: true}) {:ok, conn: build_conn(), user_role: user_role, admin_role: admin_role} end
      
      





引数として役割を取り、 role_idが追加された有効な属性の新しい辞書を返すvalid_create_attrsヘルパーを追加します



 defp valid_create_attrs(role) do Map.put(@valid_create_attrs, :role_id, role.id) end
      
      





最後に、 作成および更新アクションでこのヘルパーを使用し、辞書のuser_role値をサンプルと一致させます。



 test "creates resource and redirects when data is valid", %{conn: conn, user_role: user_role} do conn = post conn, user_path(conn, :create), user: valid_create_attrs(user_role) assert redirected_to(conn) == user_path(conn, :index) assert Repo.get_by(User, @valid_attrs) end test "updates chosen resource and redirects when data is valid", %{conn: conn, user_role: user_role} do user = Repo.insert! %User{} conn = put conn, user_path(conn, :update, user), user: valid_create_attrs(user_role) assert redirected_to(conn) == user_path(conn, :show, user) assert Repo.get_by(User, @valid_attrs) end
      
      





これですべてのユーザーコントローラーテストに合格するはずです! ただし、 mix test



実行してもエラーが表示されます。



ポストコントローラーのテストを修正します



最終的には、ユーザーとの投稿を簡単に作成できるように多数のヘルパーを追加することで、 PostControllerテストで作業することになりました。 そこで、有効なユーザーを作成できるように、ロールの概念を追加する必要があります。 test/controllers/post_controller_test.exs



一番上にPxblog.Roleへのリンクを追加することからtest/controllers/post_controller_test.exs







 alias Pxblog.Role alias Pxblog.TestHelper
      
      





次に、以前に実行したものとはわずかに異なるセットアップブロックを作成します



 setup do {:ok, role} = TestHelper.create_role(%{name: "User Role", admin: false}) {:ok, user} = TestHelper.create_user(role, %{email: "test@test.com", username: "testuser", password: "test", password_confirmation: "test"}) {:ok, post} = TestHelper.create_post(user, %{title: "Test Post", body: "Test Body"}) conn = build_conn() |> login_user(user) {:ok, conn: conn, user: user, role: role, post: post} end
      
      





ここで最初にしたことは、管理者権限のない標準の役割を作成することでした。 次の行では、この役割に基づいて、ユーザーを作成しました。 次に、このユーザーの投稿が作成されました。 すでに入り口で作品について議論したので、先に進みましょう。 最後に、作成したすべてのモデルを返すので、パターンマッチングを使用して、各テストで必要なものが選択されます。



すべてを再び緑色にするために、1つのテストを変更するだけです。 「別のユーザーの投稿を編集しようとするとリダイレクトする」テストは、ロールについて何も知らずに、その場で別のユーザーを作成しようとするためクラッシュします。 少し修正しましょう。



 test "redirects when trying to edit a post for a different user", %{conn: conn, user: user, role: role, post: post} do {:ok, other_user} = TestHelper.create_user(role, %{email: "test2@test.com", username: "test2", password: "test", password_confirmation: "test"}) 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
      
      





そのため、テスト定義でパターンマッチングを使用してロールを取得することを追加し、 other_userの作成を少し変更して、ここでTestHelperが結果のロールとともに使用されるようにしました。



パターンマッチングを使用して取得できる値の1つとしてTestHelperから投稿オブジェクトを追加することにより、リファクタリングする機会があります。 したがって、 build_postのすべての呼び出しを、この方法で取得したpostオブジェクトに変更できます。 すべての変更後、ファイルは次のようになります。



 defmodule Pxblog.PostControllerTest do use Pxblog.ConnCase alias Pxblog.Post alias Pxblog.TestHelper @valid_attrs %{body: "some content", title: "some content"} @invalid_attrs %{} setup do {:ok, role} = TestHelper.create_role(%{name: "User Role", admin: false}) {:ok, user} = TestHelper.create_user(role, %{email: "test@test.com", username: "testuser", password: "test", password_confirmation: "test"}) {:ok, post} = TestHelper.create_post(user, %{title: "Test Post", body: "Test Body"}) conn = build_conn() |> login_user(user) {:ok, conn: conn, user: user, role: role, post: post} end defp login_user(conn, user) do post conn, session_path(conn, :create), user: %{username: user.username, password: user.password} end 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 "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 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 test "shows chosen resource", %{conn: conn, user: user, post: post} do conn = get conn, user_post_path(conn, :show, user, post) assert html_response(conn, 200) =~ "Show post" end test "renders page not found when id is nonexistent", %{conn: conn, user: user} do assert_error_sent 404, fn -> get conn, user_post_path(conn, :show, user, -1) end end test "renders form for editing chosen resource", %{conn: conn, user: user, post: post} do 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, post: post} do 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, post: post} do 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, post: post} do 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 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 test "redirects when trying to edit a post for a different user", %{conn: conn, role: role, post: post} do {:ok, other_user} = TestHelper.create_user(role, %{email: "test2@test.com", username: "test2", password: "test", password_confirmation: "test"}) 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 end
      
      





Session Controllerのテストの修復



test/controllers/session_controller_test.exs



一部のテストは、 TestHelperの使用を開始するように指示しなかったため、パスしません。 前と同様に、ファイルの先頭にエイリアスを追加し、 セットアップブロックを変更します



 defmodule Pxblog.SessionControllerTest do use Pxblog.ConnCase alias Pxblog.User alias Pxblog.TestHelper setup do {:ok, role} = TestHelper.create_role(%{name: "user", admin: false}) {:ok, _user} = TestHelper.create_user(role, %{username: "test", password: "test", password_confirmation: "test", email: "test@test.com"}) {:ok, conn: build_conn()} end
      
      





テストが合格し始めるにはこれで十分です! やった!



残りのテストを修正します



まだ2つのテストが壊れています。 それで、それらを緑にしましょう!



 1) test current user returns the user in the session (Pxblog.LayoutViewTest) test/views/layout_view_test.exs:13 Expected truthy, got nil code: LayoutView.current_user(conn) stacktrace: test/views/layout_view_test.exs:15 2) test current user returns nothing if there is no user in the session (Pxblog.LayoutViewTest) test/views/layout_view_test.exs:18 ** (ArgumentError) cannot convert nil to param stacktrace: (phoenix) lib/phoenix/param.ex:67: Phoenix.Param.Atom.to_param/1 (pxblog) web/router.ex:1: Pxblog.Router.Helpers.session_path/4 test/views/layout_view_test.exs:20
      
      





ファイルtest/views/layout_view_test.exs



一番上で、ロールなしでユーザーがどのように作成されるかを見ることができます! セットアップブロックでは、このユーザーを返さないため、このような悲しい結果につながります。 ホラー! それでは、ファイル全体をすばやくリファクタリングしましょう。



 defmodule Pxblog.LayoutViewTest do use Pxblog.ConnCase, async: true alias Pxblog.LayoutView alias Pxblog.TestHelper setup do {:ok, role} = TestHelper.create_role(%{name: "User Role", admin: false}) {:ok, user} = TestHelper.create_user(role, %{email: "test@test.com", username: "testuser", password: "test", password_confirmation: "test"}) {:ok, conn: build_conn(), user: user} end test "current user returns the user in the session", %{conn: conn, user: user} do conn = post conn, session_path(conn, :create), user: %{username: user.username, password: user.password} assert LayoutView.current_user(conn) end test "current user returns nothing if there is no user in the session", %{conn: conn, user: user} do conn = delete conn, session_path(conn, :delete, user) refute LayoutView.current_user(conn) end end
      
      





ここでは、 Roleモデルのエイリアスを追加し、有効なロールを作成し、このロールを持つ有効なユーザーを作成してから、 connオブジェクトを持つ結果のユーザーを返します。 最後に、両方のテスト関数で、パターンマッチングを使用してユーザーを取得します。 mix test



を実行して...



すべてのテストは緑色です! しかし、いくつかの警告が表示されました(クリーンなコードを懸念して上書きしすぎたため)。



 test/controllers/post_controller_test.exs:20: warning: function create_user/0 is unused test/views/layout_view_test.exs:6: warning: unused alias Role test/views/layout_view_test.exs:5: warning: unused alias User test/controllers/user_controller_test.exs:5: warning: unused alias Role test/controllers/post_controller_test.exs:102: warning: variable user is unused test/controllers/post_controller_test.exs:6: warning: unused alias Role
      
      





修正するには、これらの各ファイルに移動して、問題のあるエイリアスと関数を削除します。 もう必要ありません!



 $ mix test
      
      





結論:



 ......................................... Finished in 0.4 seconds 41 tests, 0 failures Randomized with seed 588307
      
      





初期管理データを作成する



最終的には、管理者のみが新しいユーザーを作成できるようにする必要があります。 ただし、これにより、最初はユーザーまたは管理者を作成できない状況が発生します。 管理者のデフォルトのシードデータを追加することで、この病気を治すことができます。 これを行うには、 priv/repo/seeds.exs



を開き、次のコードを貼り付けpriv/repo/seeds.exs







 alias Pxblog.Repo alias Pxblog.Role alias Pxblog.User role = %Role{} |> Role.changeset(%{name: "Admin Role", admin: true}) |> Repo.insert! admin = %User{} |> User.changeset(%{username: "admin", email: "admin@test.com", password: "test", password_confirmation: "test", role_id: role.id}) |> Repo.insert!
      
      





そして、次のコマンドでシードをロードします。



 $ mix run priv/repo/seeds.exs
      
      





次に起こること



モデルが構成され、ロールと対話する準備が整い、すべてのテストが再び緑色になったので、コントローラーに機能を追加して、ユーザーに実行する権限がない場合に特定の操作を制限する必要があります。 次の投稿では、この機能を実装する最善の方法、ヘルパーモジュールを追加する方法、そしてもちろんテストをグリーンに保つ方法を検討します。



シリーズの他の記事



  1. エントリー
  2. ログイン
  3. 役割を追加
  4. コントローラーで役割を処理します
  5. ExMachinaを接続します
  6. マークダウンのサポート
  7. コメントを追加
  8. コメントで終了
  9. チャンネル
  10. チャネルテスト
  11. おわりに




Wunschからの結論



一度に2つの素晴らしいニュースがあります! まず、私たちがもっといます。これにより、 すべてのサブスクライバーは、 Elixirが開発者をどのように引き付け、 それによって彼らを引き留めるかについて、明日このサイクルには適用されません)の メールで新しい記事を受け取り ます 。 したがって、まだサインアップしていない場合は、時間を無駄にしないでください



第二に、私たちは非常に有用な贈り物をすることに決めました-デイブ・トーマスによる本“ Programming Elixir” 。 Dave Thomasのプログラミングに関する教育文学の第一人者からの( 初心者だけでなく)言語の優れた紹介です。 入手するには、Elixir言語でHabrahabrに投稿し、記事がWunsh.ruのコンテスト専用に公開されたことを示す必要があります。 勝者は、記事が最高の評価を得た人です。 詳細な条件については、こちらをご覧ください



その他の部品:

  1. エントリー
  2. ログイン
  3. 役割を追加する
  4. 近日公開予定...


研究の成功、私たちと一緒に!



All Articles