PhoenixずElixirを䜿甚しおブログ゚ンゞンを䜜成する/パヌト4.コントロヌラヌにロヌル凊理を远加する





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



このパヌトでは、ロヌルを䜿甚しおアクセス暩の分離を完了したす。 このシリヌズの蚘事の重芁なポむントは、テストに倚くの泚意が払われおいるこずであり、テストは玠晎らしいです



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





私たちが泊たった堎所



前回は、モデルの内郚にロヌルの抂念を远加し、テストを簡単にするためにテスト甚の補助関数を䜜成する方法を分けたした。 次に、ロヌルベヌスの制玄をコントロヌラヌに远加する必芁がありたす。 任意のコントロヌラヌで䜿甚できるヘルパヌ関数を䜜成するこずから始めたしょう。



ロヌルをチェックするためのヘルパヌ関数を䜜成する



今日の最初のステップは、管理者暩限の簡単なナヌザヌチェックを䜜成するこずです。 これを行うには、 web/models/role_checker.ex



を䜜成し、次のコヌドを入力したす



 defmodule Pxblog.RoleChecker do alias Pxblog.Repo alias Pxblog.Role def is_admin?(user) do (role = Repo.get(Role, user.role_id)) && role.admin end end
      
      





たた、この機胜をカバヌするテストを䜜成しおみたしょう。 ファむルtest/models/role_checker_test.exs



開きたす



 defmodule Pxblog.RoleCheckerTest do use Pxblog.ModelCase alias Pxblog.TestHelper alias Pxblog.RoleChecker test "is_admin? is true when user has an admin role" do {:ok, role} = TestHelper.create_role(%{name: "Admin", admin: true}) {:ok, user} = TestHelper.create_user(role, %{email: "test@test.com", username: "user", password: "test", password_confirmation: "test"}) assert RoleChecker.is_admin?(user) end test "is_admin? is false when user does not have an admin role" do {:ok, role} = TestHelper.create_role(%{name: "User", admin: false}) {:ok, user} = TestHelper.create_user(role, %{email: "test@test.com", username: "user", password: "test", password_confirmation: "test"}) refute RoleChecker.is_admin?(user) end end
      
      





最初のテストでは管理者を䜜成し、2番目のテストでは通垞のナヌザヌです。 最埌に、関数is_admin?



を確認しis_admin?



最初のtrue



はtrue



、2番目の堎合はfalse



を返しtrue



。 is_admin?



関数はis_admin?



RoleChecker



モゞュヌルからナヌザヌが必芁な堎合、パフォヌマンスをチェックする非垞に簡単なテストを䜜成できたす。 私たちが確信できるコヌドが刀明したした テストを実行し、緑色のたたであるこずを確認したす。



管理者のみにナヌザヌの远加を蚱可する



以前は、 UserController



に制限を远加しなかったため、今がauthorize_user



を有効にするずきです。 これから䜕をするかをすぐに蚈画したしょう。 ナヌザヌは自分のプロファむルを線集、曎新、削陀できたすが、新しいナヌザヌを远加できるのは管理者だけです。



web/controllers/user_controller.ex



scrub_params



web/controllers/user_controller.ex



scrub_params



行の䞋に、次を远加したす。



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





たた、ファむルの䞋郚に、ナヌザヌ認蚌ず管理者認蚌を凊理するプラむベヌト関数をいく぀か远加したす。



 defp authorize_user(conn, _) do user = get_session(conn, :current_user) if user && (Integer.to_string(user.id) == conn.params["id"] || Pxblog.RoleChecker.is_admin?(user)) do conn else conn |> put_flash(:error, "You are not authorized to modify that user!") |> redirect(to: page_path(conn, :index)) |> halt() end end defp authorize_admin(conn, _) do user = get_session(conn, :current_user) if user && Pxblog.RoleChecker.is_admin?(user) do conn else conn |> put_flash(:error, "You are not authorized to create new users!") |> redirect(to: page_path(conn, :index)) |> halt() end end
      
      





authorize_user



呌び出しは、 PostController



をチェックするこずを陀いお、 PostController



にあったものず本質的に同じRoleChecker.is_admin?



。



authorize_admin



関数はさらにシンプルです。 珟圚のナヌザヌが管理者であるこずのみを確認したす。



ファむルtest/controllers/user_controller_test.exs



戻っお、新しい条件を考慮に入れるようにテストを倉曎したしょう。



セットアップブロックを倉曎するこずから始めたしょう。



 setup do {:ok, user_role} = TestHelper.create_role(%{name: "user", admin: false}) {:ok, nonadmin_user} = TestHelper.create_user(user_role, %{email: "nonadmin@test.com", username: "nonadmin", password: "test", password_confirmation: "test"}) {:ok, admin_role} = TestHelper.create_role(%{name: "admin", admin: true}) {:ok, admin_user} = TestHelper.create_user(admin_role, %{email: "admin@test.com", username: "admin", password: "test", password_confirmation: "test"}) {:ok, conn: build_conn(), admin_role: admin_role, user_role: user_role, nonadmin_user: nonadmin_user, admin_user: admin_user} end
      
      





その内郚にナヌザヌロヌル、管理者のロヌル、通垞のナヌザヌおよび管理者を䜜成し、それらを返したす。 したがっお、サンプルず比范するこずで、テストでそれらを䜿甚する機䌚が埗られたす。 ログむンするためのヘルパヌ関数も必芁になるため、 login_user



からPostController



関数をコピヌしたす。



 defp login_user(conn, user) do post conn, session_path(conn, :create), user: %{username: user.username, password: user.password} end
      
      





index



アクションに制限を远加しないため、このテストをスキップできたす。 次のテスト「新しいリ゜ヌスのフォヌムをレンダリングする」 new



アクションを衚すでは、制限が課されたす。 ナヌザヌには管理者暩限が必芁です。



次のコヌドに䞀臎するようにテストを倉曎したす。



 @tag admin: true test "renders form for new resources", %{conn: conn, admin_user: admin_user} do conn = conn |> login_user(admin_user) |> get(user_path(conn, :new)) assert html_response(conn, 200) =~ "New user" end
      
      





このテストの䞊に@tag admin: true



を远加しお、 @tag admin: true



ずしおマヌクしたす。 したがっお、セット党䜓ではなく、同様のテストのみを実行できたす。 詊しおみたしょう



 mix test --only admin
      
      





出力では、゚ラヌが衚瀺されたす。



 1) test renders form for new resources (Pxblog.UserControllerTest) test/controllers/user_controller_test.exs:26 ** (KeyError) key :role_id not found in: %{id: 348, username: “admin”} stacktrace: (pxblog) web/models/role_checker.ex:6: Pxblog.RoleChecker.is_admin?/1 (pxblog) web/controllers/user_controller.ex:84: Pxblog.UserController.authorize_admin/2 (pxblog) web/controllers/user_controller.ex:1: Pxblog.UserController.phoenix_controller_pipeline/2 (pxblog) lib/phoenix/router.ex:255: Pxblog.Router.dispatch/2 (pxblog) web/router.ex:1: Pxblog.Router.do_call/2 (pxblog) lib/pxblog/endpoint.ex:1: Pxblog.Endpoint.phoenix_pipeline/1 (pxblog) lib/phoenix/endpoint/render_errors.ex:34: Pxblog.Endpoint.call/2 (phoenix) lib/phoenix/test/conn_test.ex:193: Phoenix.ConnTest.dispatch/5 test/controllers/user_controller_test.exs:28
      
      





ここでの問題は、完党なナヌザヌモデルをRoleChecker.is_admin?



関数に枡しおいないこずRoleChecker.is_admin?



。 そしお、 sign_in



モゞュヌルのsign_in



関数からcurrent_user



関数が受け取ったデヌタの小さなサブセットをsign_in



たす。



同様にrole_id



を远加したしょう。 以䞋に瀺すように、 web/controllers/session_controller.ex



ファむルに倉曎を加えたした。



 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, role_id: user.role_id}) |> put_flash(:info, "Sign in successful!") |> redirect(to: page_path(conn, :index)) else failed_login(conn) end end
      
      





もう䞀床、 admin



タグを䜿甚しおテストを実行しおみたしょう。



 $ mix test --only admin
      
      





再び緑 ナヌザヌが管理者ではないが、同時にUserController



新しいアクションを入力しようずする堎合、逆の状況のテストを䜜成する必芁がありたす。 ファむルtest/controllers/user_controller_test.exs



戻りたす。



 @tag admin: true test "redirects from new form when not admin", %{conn: conn, nonadmin_user: nonadmin_user} do conn = login_user(conn, nonadmin_user) conn = get conn, user_path(conn, :new) assert get_flash(conn, :error) == "You are not authorized to create new users!" assert redirected_to(conn) == page_path(conn, :index) assert conn.halted end
      
      





create



アクションに぀いおも同じこずを行いcreate



。 䞡方のケヌスに察しお1぀のテストを䜜成したす。



 @tag admin: true test "creates resource and redirects when data is valid", %{conn: conn, user_role: user_role, admin_user: admin_user} do conn = login_user(conn, admin_user) 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 @tag admin: true test "redirects from creating user when not admin", %{conn: conn, user_role: user_role, nonadmin_user: nonadmin_user} do conn = login_user(conn, nonadmin_user) conn = post conn, user_path(conn, :create), user: valid_create_attrs(user_role) assert get_flash(conn, :error) == "You are not authorized to create new users!" assert redirected_to(conn) == page_path(conn, :index) assert conn.halted end @tag admin: true test "does not create resource and renders errors when data is invalid", %{conn: conn, admin_user: admin_user} do conn = login_user(conn, admin_user) conn = post conn, user_path(conn, :create), user: @invalid_attrs assert html_response(conn, 200) =~ "New user" end
      
      





show



アクションをスキップできたす 圌に新しい条件を远加したせんでした。 user_controller_test.exs



ファむルが次のようになるたで、同じパタヌンに埓いたす。



 defmodule Pxblog.UserControllerTest do use Pxblog.ConnCase alias Pxblog.User alias Pxblog.TestHelper @valid_create_attrs %{email: "test@test.com", username: "test", password: "test", password_confirmation: "test"} @valid_attrs %{email: "test@test.com", username: "test"} @invalid_attrs %{} setup do {:ok, user_role} = TestHelper.create_role(%{name: "user", admin: false}) {:ok, nonadmin_user} = TestHelper.create_user(user_role, %{email: "nonadmin@test.com", username: "nonadmin", password: "test", password_confirmation: "test"}) {:ok, admin_role} = TestHelper.create_role(%{name: "admin", admin: true}) {:ok, admin_user} = TestHelper.create_user(admin_role, %{email: "admin@test.com", username: "admin", password: "test", password_confirmation: "test"}) {:ok, conn: build_conn(), admin_role: admin_role, user_role: user_role, nonadmin_user: nonadmin_user, admin_user: admin_user} end defp valid_create_attrs(role) do Map.put(@valid_create_attrs, :role_id, role.id) 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} do conn = get conn, user_path(conn, :index) assert html_response(conn, 200) =~ "Listing users" end @tag admin: true test "renders form for new resources", %{conn: conn, admin_user: admin_user} do conn = login_user(conn, admin_user) conn = get conn, user_path(conn, :new) assert html_response(conn, 200) =~ "New user" end @tag admin: true test "redirects from new form when not admin", %{conn: conn, nonadmin_user: nonadmin_user} do conn = login_user(conn, nonadmin_user) conn = get conn, user_path(conn, :new) assert get_flash(conn, :error) == "You are not authorized to create new users!" assert redirected_to(conn) == page_path(conn, :index) assert conn.halted end @tag admin: true test "creates resource and redirects when data is valid", %{conn: conn, user_role: user_role, admin_user: admin_user} do conn = login_user(conn, admin_user) 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 @tag admin: true test "redirects from creating user when not admin", %{conn: conn, user_role: user_role, nonadmin_user: nonadmin_user} do conn = login_user(conn, nonadmin_user) conn = post conn, user_path(conn, :create), user: valid_create_attrs(user_role) assert get_flash(conn, :error) == "You are not authorized to create new users!" assert redirected_to(conn) == page_path(conn, :index) assert conn.halted end @tag admin: true test "does not create resource and renders errors when data is invalid", %{conn: conn, admin_user: admin_user} do conn = login_user(conn, admin_user) conn = post conn, user_path(conn, :create), user: @invalid_attrs assert html_response(conn, 200) =~ "New user" end 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 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 @tag admin: true test "renders form for editing chosen resource when logged in as that user", %{conn: conn, nonadmin_user: nonadmin_user} do conn = login_user(conn, nonadmin_user) conn = get conn, user_path(conn, :edit, nonadmin_user) assert html_response(conn, 200) =~ "Edit user" end @tag admin: true test "renders form for editing chosen resource when logged in as an admin", %{conn: conn, admin_user: admin_user, nonadmin_user: nonadmin_user} do conn = login_user(conn, admin_user) conn = get conn, user_path(conn, :edit, nonadmin_user) assert html_response(conn, 200) =~ "Edit user" end @tag admin: true test "redirects away from editing when logged in as a different user", %{conn: conn, nonadmin_user: nonadmin_user, admin_user: admin_user} do conn = login_user(conn, nonadmin_user) conn = get conn, user_path(conn, :edit, admin_user) assert get_flash(conn, :error) == "You are not authorized to modify that user!" assert redirected_to(conn) == page_path(conn, :index) assert conn.halted end @tag admin: true test "updates chosen resource and redirects when data is valid when logged in as that user", %{conn: conn, nonadmin_user: nonadmin_user} do conn = login_user(conn, nonadmin_user) conn = put conn, user_path(conn, :update, nonadmin_user), user: @valid_create_attrs assert redirected_to(conn) == user_path(conn, :show, nonadmin_user) assert Repo.get_by(User, @valid_attrs) end @tag admin: true test "updates chosen resource and redirects when data is valid when logged in as an admin", %{conn: conn, admin_user: admin_user} do conn = login_user(conn, admin_user) conn = put conn, user_path(conn, :update, admin_user), user: @valid_create_attrs assert redirected_to(conn) == user_path(conn, :show, admin_user) assert Repo.get_by(User, @valid_attrs) end @tag admin: true test "does not update chosen resource when logged in as different user", %{conn: conn, nonadmin_user: nonadmin_user, admin_user: admin_user} do conn = login_user(conn, nonadmin_user) conn = put conn, user_path(conn, :update, admin_user), user: @valid_create_attrs assert get_flash(conn, :error) == "You are not authorized to modify that user!" assert redirected_to(conn) == page_path(conn, :index) assert conn.halted end @tag admin: true test "does not update chosen resource and renders errors when data is invalid", %{conn: conn, nonadmin_user: nonadmin_user} do conn = login_user(conn, nonadmin_user) conn = put conn, user_path(conn, :update, nonadmin_user), user: @invalid_attrs assert html_response(conn, 200) =~ "Edit user" end @tag admin: true test "deletes chosen resource when logged in as that user", %{conn: conn, user_role: user_role} do {:ok, user} = TestHelper.create_user(user_role, @valid_create_attrs) conn = login_user(conn, user) |> delete(user_path(conn, :delete, user)) assert redirected_to(conn) == user_path(conn, :index) refute Repo.get(User, user.id) end @tag admin: true test "deletes chosen resource when logged in as an admin", %{conn: conn, user_role: user_role, admin_user: admin_user} do {:ok, user} = TestHelper.create_user(user_role, @valid_create_attrs) conn = login_user(conn, admin_user) |> delete(user_path(conn, :delete, user)) assert redirected_to(conn) == user_path(conn, :index) refute Repo.get(User, user.id) end @tag admin: true test "redirects away from deleting chosen resource when logged in as a different user", %{conn: conn, user_role: user_role, nonadmin_user: nonadmin_user} do {:ok, user} = TestHelper.create_user(user_role, @valid_create_attrs) conn = login_user(conn, nonadmin_user) |> delete(user_path(conn, :delete, user)) assert get_flash(conn, :error) == "You are not authorized to modify that user!" assert redirected_to(conn) == page_path(conn, :index) assert conn.halted end end
      
      





テストセット党䜓を起動したす。 圌らはすべお再び通過したす



管理者が投皿を倉曎できるようにしたす



幞いなこずに、私たちはすでにほずんどの䜜業を完了しおおり、この最埌の郚分だけが残っおいたす。 䜜業が完了するず、管理者の機胜が完党に準備できたす。 web/controllers/post_controller.ex



を開き、 authorize_user



関数を倉曎しおRoleChecker.is_admin?



ヘルパヌ関数RoleChecker.is_admin?



䜿甚しRoleChecker.is_admin?



。 ナヌザヌが管理者である堎合、ナヌザヌ投皿の倉曎を完党に制埡できたす。



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





test/controllers/post_controller_test.exs



、ファむルtest/controllers/post_controller_test.exs



、承認ルヌルをカバヌするためにいく぀かのテストを远加したす。



 test "redirects when trying to delete 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 = delete conn, user_post_path(conn, :delete, 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 test "renders form for editing chosen resource when logged in as admin", %{conn: conn, user: user, post: post} do {:ok, role} = TestHelper.create_role(%{name: "Admin", admin: true}) {:ok, admin} = TestHelper.create_user(role, %{username: "admin", email: "admin@test.com", password: "test", password_confirmation: "test"}) conn = login_user(conn, admin) |> get(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 when logged in as admin", %{conn: conn, user: user, post: post} do {:ok, role} = TestHelper.create_role(%{name: "Admin", admin: true}) {:ok, admin} = TestHelper.create_user(role, %{username: "admin", email: "admin@test.com", password: "test", password_confirmation: "test"}) conn = login_user(conn, admin) |> put(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 when logged in as admin", %{conn: conn, user: user, post: post} do {:ok, role} = TestHelper.create_role(%{name: "Admin", admin: true}) {:ok, admin} = TestHelper.create_user(role, %{username: "admin", email: "admin@test.com", password: "test", password_confirmation: "test"}) conn = login_user(conn, admin) |> put(user_post_path(conn, :update, user, post), post: %{"body" => nil}) assert html_response(conn, 200) =~ "Edit post" end test "deletes chosen resource when logged in as admin", %{conn: conn, user: user, post: post} do {:ok, role} = TestHelper.create_role(%{name: "Admin", admin: true}) {:ok, admin} = TestHelper.create_user(role, %{username: "admin", email: "admin@test.com", password: "test", password_confirmation: "test"}) conn = login_user(conn, admin) |> delete(user_post_path(conn, :delete, user, post)) assert redirected_to(conn) == user_post_path(conn, :index, user) refute Repo.get(Post, post.id) end
      
      





珟圚、私たちのブログ゚ンゞンは党䜓ずしおスムヌズに動䜜しおいたすが、いく぀かのバグがありたす。 たぶん圌らは間違っお珟れた。 たたは途䞭で䜕かを忘れたした。 したがっお、それらを識別しお排陀したしょう。 たた、アプリケヌションが可胜なすべおの最新バヌゞョンで実行されるように、すべおの䟝存関係を曎新したしょう。



新しいナヌザヌを远加するず、圹割が芋぀からないずいう゚ラヌが衚瀺される



これは、 nolotusがPxblogペヌゞ https://github.com/Diamond/pxblogで 発芋したした。 ありがずう



part_3



ブランチpart_3



は、新しいナヌザヌを䜜成しようずするず、ロヌルがないため゚ラヌが発生したすナヌザヌの䜜成時にrole_id



の存圚をrole_id



にしたため。 最初に問題を調べおから、修正を始めたしょう。 管理者ずしおログむンし、アドレス/users/new



に移動し、すべおのフィヌルドに入力しおボタンをクリックするず、次の゚ラヌが衚瀺されたす。





これは、ナヌザヌに名前、メヌル、パスワヌド、パスワヌド確認の入力を芁求するために起こりたす。 しかし、圹割に぀いおは䜕も蚀いたせん。 これを知っお、解決策に進みたしょう。 フォヌムで遞択可胜なロヌルのリストを枡すこずから始めたしょう。



new, create, edit update



各アクションで必芁になりnew, create, edit update



。 alias Pxblog.Role



がただ存圚しない堎合は、UserController alias Pxblog.Role



先頭ファむルweb/controllers/user_controller.ex



alias Pxblog.Role



远加したす。 次に、前述のすべおのアクションに倉曎を加えたす。



 def new(conn, _params) do roles = Repo.all(Role) changeset = User.changeset(%User{}) render(conn, "new.html", changeset: changeset, roles: roles) end def edit(conn, %{"id" => id}) do roles = Repo.all(Role) user = Repo.get!(User, id) changeset = User.changeset(user) render(conn, "edit.html", user: user, changeset: changeset, roles: roles) end def create(conn, %{"user" => user_params}) do roles = Repo.all(Role) changeset = User.changeset(%User{}, user_params) case Repo.insert(changeset) do {:ok, _user} -> conn |> put_flash(:info, "User created successfully.") |> redirect(to: user_path(conn, :index)) {:error, changeset} -> render(conn, "new.html", changeset: changeset, roles: roles) end end def update(conn, %{"id" => id, "user" => user_params}) do roles = Repo.all(Role) user = Repo.get!(User, id) changeset = User.changeset(user, user_params) case Repo.update(changeset) do {:ok, user} -> conn |> put_flash(:info, "User updated successfully.") |> redirect(to: user_path(conn, :show, user)) {:error, changeset} -> render(conn, "edit.html", user: user, changeset: changeset, roles: roles) end end
      
      





それぞれに぀いお、 Repo.all(Role)



を䜿甚しおすべおのロヌルを遞択し、 assigns



リストに远加したこずに泚意しおください゚ラヌの堎合を含む。



Phoenix.Html



フォヌムのヘルパヌ関数を䜿甚しおドロップダりンリストを実装する必芁もありたす。 それでは、ドキュメントでこれがどのように行われるか芋おみたしょう



 select(form, field, values, opts \\ []) Generates a select tag with the given values.
      
      





ドロップダりンリストは、 values



匕数ずしお通垞のリスト [value, value, value]



の圢匏たたはキヌワヌドのリスト [displayed: value, displayed: value]



の圢匏のいずれかを想定しおいvalues



。 この堎合、ロヌル名を衚瀺するず同時に、フォヌムを送信するずきに遞択したロヌルの識別子の倀を枡す必芁がありたす。 リストされおいるどのフォヌマットにも適合しないため、 @roles



倉数を単玔にヘルパヌ関数に単玔にスロヌするこずはできたせん。 それでは、タスクを簡玠化する関数をView



で曞きたしょう。



 defmodule Pxblog.UserView do use Pxblog.Web, :view def roles_for_select(roles) do roles |> Enum.map(&["#{&1.name}": &1.id]) |> List.flatten end end
      
      





roles_for_select



関数を远加したした。 roles_for_select



関数は、単にロヌルのコレクションを受け入れたす。 この関数の機胜の行を芋おみたしょう。 連鎖の次の関数に枡す圹割コレクションから始めたしょう。



 Enum.map(&["#{&1.name}": &1.id])
      
      





&/&1



は匿名関数の略語であり、完党版では次のように曞き換えるこずができるこずを思い出しおください。



 Enum.map(roles, fn role -> ["#{role.name}": role.id] end)
      
      





map



操䜜を開始しお、より小さなキヌリストのリストを返したした。ロヌル名はキヌで、ロヌル識別子は倀です。



ロヌルの初期倀が䞎えられたず仮定したす



 roles = [%Role{name: "Admin Role", id: 1}, %Role{name: "User Role", id: 2}]
      
      





この堎合、マップ関数を呌び出すず、次のようなリストが返されたす。



 [["Admin Role": 1], ["User Role": 2]]
      
      





次に、䞍必芁なネストを削陀する最埌のList.flatten



関数にList.flatten



たす。 したがっお、最終結果は次のずおりです。



 ["Admin Role": 1, "User Role": 2]
      
      





これは、ドロップダりンリストの補助機胜に必芁な圢匏であるこずがたたたたありたした 肩をshoulder web/templates/user/new.html.eex



こずはできたせんが、 web/templates/user/new.html.eex



のテンプレヌトを倉曎する必芁がありweb/templates/user/new.html.eex







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





そしお、 web/templates/user/edit.html.eex







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





そしお最埌に、新しいヘルパヌ関数をweb/templates/user/form.html.eex



に远加するこずを拒吊しないず思いweb/templates/user/form.html.eex



。 その結果、ナヌザヌが翻蚳できるすべおのロヌルを含むドロップダりンリストがフォヌムに衚瀺されたす。 [ 送信 ]ボタンの前に次のコヌドを远加したす。



 <div class="form-group"> <%= label f, :role_id, "Role", class: "control-label" %> <%= select f, :role_id, roles_for_select(@roles), class: "form-control" %> <%= error_tag f, :role_id %> </div>
      
      





これで、新しいナヌザヌを远加するか、既存のナヌザヌを線集しようずするず、このナヌザヌに圹割を割り圓おる機䌚が埗られたす 最埌のバグが残っおいたす



初期デヌタをダりンロヌドするず、それらが連続しお数回耇補されたす



珟圚、初期デヌタを数回ロヌドするず、重耇が発生したすが、これぱラヌです。 ヘルパヌ匿名のfind_or_create



関数をいく぀か曞いおみたしょう。



 alias Pxblog.Repo alias Pxblog.Role alias Pxblog.User import Ecto.Query, only: [from: 2] find_or_create_role = fn role_name, admin -> case Repo.all(from r in Role, where: r.name == ^role_name and r.admin == ^admin) do [] -> %Role{} |> Role.changeset(%{name: role_name, admin: admin}) |> Repo.insert!() _ -> IO.puts "Role: #{role_name} already exists, skipping" end end find_or_create_user = fn username, email, role -> case Repo.all(from u in User, where: u.username == ^username and u.email == ^email) do [] -> %User{} |> User.changeset(%{username: username, email: email, password: "test", password_confirmation: "test", role_id: role.id}) |> Repo.insert!() _ -> IO.puts "User: #{username} already exists, skipping" end end _user_role = find_or_create_role.("User Role", false) admin_role = find_or_create_role.("Admin Role", true) _admin_user = find_or_create_user.("admin", "admin@test.com", admin_role)
      
      





゚むリアスRepo



、 Role



およびUser



が远加されおいるこずに泚意しおください。 たた、 Ecto.Query



モゞュヌルfrom



関数from



むンポヌトしお、䟿利なク゚リ構文を䜿甚したす。 次に、匿名のfind_or_create_role



関数を芋おください。関数自䜓は、単に匕数ずしおロヌル名ず管理フラグを受け入れたす。



これらの基準に基づいお、ク゚リを実行したすRepo.all



条件ず䞀臎するのではなく、倀を比范するため、条件内の各倉数に続く^蚘号に泚意しおくださいwhere



。そしお、結果をcaseステヌトメントにスロヌしたす。Repo.all



䜕も芋぀からなかった堎合は、空のリストが返されるため、ロヌルを远加する必芁がありたす。それ以倖の堎合は、ロヌルが既に存圚するず想定し、ファむルの残りのダりンロヌドに進みたす。この関数find_or_create_user



は同じこずを行いたすが、異なる基準を䜿甚したす。



最埌に、これらの各関数を呌び出したす名前ず匕数の間の匿名関数の必須ポむントに泚意しおください。管理者を䜜成するには、圌の圹割を再利甚する必芁がありたす。そのため、名前の前にadmin_role



アンダヌスコアを付けたせん。埌で、初期デヌタファむルで䜿甚するuser_role



かadmin_user



、さらに䜿甚するこずもできたすが、ここでは、アンダヌスコアを参照しおこのコヌドをそのたたにしおおきたす。これにより、初期デヌタファむルがきれいできれいに芋えたす。これで、すべおが初期デヌタをロヌドする準備ができたした。



 $ mix run priv/repo/seeds.exs [debug] SELECT r0.”id”, r0.”name”, r0.”admin”, r0.”inserted_at”, r0.”updated_at” FROM “roles” AS r0 WHERE ((r0.”name” = $1) AND (r0.”admin” = $2)) [“User Role”, false] OK query=81.7ms queue=2.8ms [debug] BEGIN [] OK query=0.2ms [debug] INSERT INTO “roles” (“admin”, “inserted_at”, “name”, “updated_at”) VALUES ($1, $2, $3, $4) RETURNING “id” [false, {{2015, 11, 6}, {19, 35, 49, 0}}, “User Role”, {{2015, 11, 6}, {19, 35, 49, 0}}] OK query=0.8ms [debug] COMMIT [] OK query=0.4ms [debug] SELECT r0.”id”, r0.”name”, r0.”admin”, r0.”inserted_at”, r0.”updated_at” FROM “roles” AS r0 WHERE ((r0.”name” = $1) AND (r0.”admin” = $2)) [“Admin Role”, true] OK query=0.4ms [debug] BEGIN [] OK query=0.2ms [debug] INSERT INTO “roles” (“admin”, “inserted_at”, “name”, “updated_at”) VALUES ($1, $2, $3, $4) RETURNING “id” [true, {{2015, 11, 6}, {19, 35, 49, 0}}, “Admin Role”, {{2015, 11, 6}, {19, 35, 49, 0}}] OK query=0.4ms [debug] COMMIT [] OK query=0.3ms [debug] SELECT u0.”id”, u0.”username”, u0.”email”, u0.”password_digest”, u0.”role_id”, u0.”inserted_at”, u0.”updated_at” FROM “users” AS u0 WHERE ((u0.”username” = $1) AND (u0.”email” = $2)) [“admin”, “admin@test.com”] OK query=0.7ms [debug] BEGIN [] OK query=0.3ms [debug] INSERT INTO “users” (“email”, “inserted_at”, “password_digest”, “role_id”, “updated_at”, “username”) VALUES ($1, $2, $3, $4, $5, $6) RETURNING “id” [“admin@test.com”, {{2015, 11, 6}, {19, 35, 49, 0}}, “$2b$12$.MuPBUVe/7/9HSOsccJYUOAD5IKEB77Pgz2oTJ/UvTvWYwAGn/Li”, 2, {{2015, 11, 6}, {19, 35, 49, 0}}, “admin”] OK query=1.2ms [debug] COMMIT [] OK query=1.1ms
      
      





初めおそれらをロヌドするず、デザむンのパックが衚瀺されたすINSERT



。すごい すべおが正垞に機胜するこずを完党に確認するには、それらを再床ダりンロヌドしお、挿入操䜜が発生しおいないこずを確認したしょう。



 $ mix run priv/repo/seeds.exs Role: User Role already exists, skipping [debug] SELECT r0.”id”, r0.”name”, r0.”admin”, r0.”inserted_at”, r0.”updated_at” FROM “roles” AS r0 WHERE ((r0.”name” = $1) AND (r0.”admin” = $2)) [“User Role”, false] OK query=104.8ms queue=3.6ms Role: Admin Role already exists, skipping [debug] SELECT r0.”id”, r0.”name”, r0.”admin”, r0.”inserted_at”, r0.”updated_at” FROM “roles” AS r0 WHERE ((r0.”name” = $1) AND (r0.”admin” = $2)) [“Admin Role”, true] OK query=0.6ms User: admin already exists, skipping [debug] SELECT u0.”id”, u0.”username”, u0.”email”, u0.”password_digest”, u0.”role_id”, u0.”inserted_at”, u0.”updated_at” FROM “users” AS u0 WHERE ((u0.”username” = $1) AND (u0.”email” = $2)) [“admin”, “admin@test.com”] OK query=0.8ms
      
      





いいねすべおが確実に機胜したす。さらに、独自の䟿利な関数を蚘述するこずで埗られた喜びを取り消すこずはできたせんEcto



。



テストでの管理者の重耇に関する゚ラヌ



ここで、ある時点でテストデヌタベヌスをリセットするず、「ナヌザヌは既に存圚したす」ずいう゚ラヌが衚瀺されたす。これを修正する簡単なそしお䞀時的な方法を提案したす。ファむルtest/support/test_helper.ex



を開き、関数を倉曎したすcreate_user



。



 def create_user(role, %{email: email, username: username, password: password, password_confirmation: password_confirmation}) do if user = Repo.get_by(User, username: username) do Repo.delete(user) end role |> build_assoc(:users) |> User.changeset(%{email: email, username: username, password: password, password_confirmation: password_confirmation}) |> Repo.insert end
      
      







䜕に来たの



これで、ナヌザヌ、投皿、圹割だけでなく、完党にグリヌンなテストができたした。ナヌザヌ登録、ナヌザヌの倉曎、投皿に実行可胜な制限を蚭けたした。そしお、圌らはいく぀かの䟿利なヘルパヌ関数を远加したした。今埌の投皿では、ブログ゚ンゞンに新しいクヌルな機胜を远加するこずに時間を割きたす



Wunschからの結論



毎週私たちはどんどん増えおおり、これは喜ばしいこずです友人、コミュニティぞの関心に感謝し、自信を衚明したした。私たちはそれを正圓化し、週に数回新しい興味深い玠材をアップロヌドしようずしたす。したがっお、Elixirに関するロシア語のニュヌスレタヌをただ賌読しおいない堎合は、時間を無駄にしないでください。今すぐ登録するず明日、新しい限定蚘事が届きたす特にあなたのために、私たちは文字通り䞀晩䞭働いおいたす。



たた、デむブ・トヌマスの新しいカリカリ本「Programming Elixir」の賞品ずしおコンテストを開催しおいたす。参加しお、勝぀こずはそれほど難しくありたせん



たた、あなたがそれを奜きなら、プロを眮き、友人に蚘事を転送するこずを忘れないでください。たたはあなたが私たちの掻動が奜きなら。結局のずころ、非垞に倚くのナヌザヌを迅速に収集するほど、玠敵な蚀語Elixirに関するほずんどの質問をカバヌするサむトのフルバヌゞョンを迅速に立ち䞊げるこずができたす。



シリヌズの他の蚘事



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




研究の成功、私たちず䞀緒に



All Articles