演習: ユーザー登録・設定機能の追加

本ガイドでは、Railsをはじめようで作成した練習用アプリ「store」にユーザー登録とユーザー設定機能を追加する方法について解説します。本ガイドでは、『Railsをはじめよう』の最終コードを出発点とします。

このガイドの内容:

  • ユーザーのアカウント登録機能を追加
  • コントローラーアクションのレート制限
  • ネステッドレイアウトの作成
  • ロール(ユーザーと管理者)ごとにコントローラを分ける
  • ロールの異なるユーザーに対するテストの作成

目次

  1. はじめに
  2. ユーザー登録機能を追加する
  3. パスワードの編集機能
  4. ユーザープロファイルを編集する
  5. アカウントを削除する
  6. メールアドレスの更新機能を追加する
  7. 管理者とユーザーを分離する
  8. すべてのユーザーを表示する
  9. Productsコントローラを分離する
  10. テストを追加する
  11. production環境にデプロイする
  12. 今後の機能
  13. 関連リンク

1 はじめに

ユーザー登録(sign up)機能は、新しいユーザーを登録する処理であり、アプリケーションに追加する最も一般的な機能の1つです。Railsをはじめようガイドで構築したeコマースアプリケーションには認証機能しかなく、ユーザーを登録するにはRailsコンソールやスクリプトで作成しなければなりません。

この機能は、他の機能を追加する前に実装しておく必要があります。たとえば、ユーザーがウィッシュリストを作成可能にするには、まずユーザーが登録できる必要があります。その後、アカウントに関連付けられたウィッシュリストを作成できます。

それでは始めましょう。

2 ユーザー登録機能を追加する

認証機能ジェネレータでユーザーを自分のアカウントにログインさせる機能は、既ににRailsをはじめようガイドで使いました。認証機能ジェネレータを用いて、Userモデルを作成し、データベースにemail_address:stringpassword_digest:stringのカラムを追加しました。また、Userモデルにhas_secure_passwordメソッドを追加し、パスワードと確認を処理します。これにより、ユーザー登録機能をアプリケーションに追加するために必要な処理はほぼ完了します。

2.1 ユーザーに名前を追加する

登録時は、ユーザーの名前も保存しておくとよいでしょう。これにより、アプリケーション内でユーザー体験をパーソナライズし、ユーザーを「XX様」のように直接名前で呼びかけることが可能になります。

それでは、データベースにfirst_namelast_nameのカラムを追加しましょう。

ターミナルで以下のコマンドを実行して、これらのカラムを持つマイグレーションを作成します。

 $ bin/rails g migration AddNamesToUsers first_name:string last_name:string 

続いてデータベースのマイグレーションを実行します。

 $ bin/rails db:migrate 

first_namelast_nameをつなげるメソッドも追加して、ユーザーのフルネームを表示できるようにしておきましょう。

app/models/user.rbファイルを開いて、以下を追加します。

 class User < ApplicationRecord has_secure_password has_many :sessions, dependent: :destroy normalizes :email_address, with: ->(e) { e.strip.downcase } validates :first_name, :last_name, presence: true def full_name "#{first_name} #{last_name}" end end 

has_secure_passwordメソッドは、パスワードが存在することだけをバリデーションします。セキュリティを強化するため、パスワードの最小文字数のチェックやパスワードが十分複雑かどうかのバリデーションも追加することを検討しましょう。

次に、ユーザー登録機能を追加して新しいユーザーを登録できるようにしましょう。

2.2 ユーザー登録用のルーティングとコントローラ

新しいユーザーを登録するのに必要なカラムがすべて揃ったので、次のステップではユーザー登録用のルーティングとそれに対応するコントローラを作成します。

config/routes.rbにユーザー登録用のリソースを追加します。

 resource :session resources :passwords, param: :token resource :sign_up 

ここでは、/sign_upに対する単一のルーティングを作成するために、単数形のresourceを使っています。

このルーティングは、リクエストをapp/controllers/sign_ups_controller.rbに送信します。次は、そのルーティングに対応するコントローラファイルを作成しましょう。

 class SignUpsController < ApplicationController def show @user = User.new end end 

Userの新しいインスタンスを作成するために、showアクションを使っています。これはユーザー登録フォームを表示するアクションです。

次に、フォームを作成しましょう。app/views/sign_ups/show.html.erbを作成し、以下のコードを追加します。

 <h1>Sign Up</h1> <%= form_with model: @user, url: sign_up_path do |form| %> <% if form.object.errors.any? %> <div>Error: <%= form.object.errors.full_messages.first %></div> <% end %> <div> <%= form.label :first_name %> <%= form.text_field :first_name, required: true, autofocus: true, autocomplete: "given-name" %> </div> <div> <%= form.label :last_name %> <%= form.text_field :last_name, required: true, autocomplete: "family-name" %> </div> <div> <%= form.label :email_address %> <%= form.email_field :email_address, required: true, autocomplete: "email" %> </div> <div> <%= form.label :password %> <%= form.password_field :password, required: true, autocomplete: "new-password" %> </div> <div> <%= form.label :password_confirmation %> <%= form.password_field :password_confirmation, required: true, autocomplete: "new-password" %> </div> <div> <%= form.submit "Sign up" %> </div> <% end %> 

このフォームは、ユーザーの名前、メールアドレス、パスワードを収集します。autocomplete属性を用いて、ブラウザに保存されたユーザー情報に基づいて、これらのフィールドの値を自動的に補完します。

このフォームでは、model: @userと一緒にurl: sign_up_pathも指定していることにご注意ください。form_withメソッドにurl:引数を指定しない場合は、Userモデルが存在すると見なして、フォームをデフォルトで/usersに送信します。ここではフォームを/usersではなく/sign_upに送信したいので、url:を設定してデフォルトのルーティングをオーバーライドしています。

再びapp/controllers/sign_ups_controller.rbをエディタで開いて、フォームの送信を処理するcreateアクションを追加します。

 class SignUpsController < ApplicationController def show @user = User.new end def create @user = User.new(sign_up_params) if @user.save start_new_session_for(@user) redirect_to root_path else render :show, status: :unprocessable_entity end end private def sign_up_params params.expect(user: [ :first_name, :last_name, :email_address, :password, :password_confirmation ]) end end 

createアクションはパラメータを割り当てて、データベースにユーザーを保存することを試みます。保存に成功した場合はユーザーをログインさせてroot_pathにリダイレクトし、失敗した場合はエラー付きのフォームを再表示します。

ブラウザでhttps://localhost:3000/sign_upを開いて、フォームが正しく動作することを確認してみましょう。

2.3 アカウント作成時にログインなしのアクセスを必須にする

認証されたユーザーは、ログインした状態のままSignUpsControllerにアクセスして別のアカウントを作成できてしまうため、このままでは混乱を招く可能性があります。

これを修正するために、app/controllers/concerns/authentication.rbファイルのAuthenticationモジュールにヘルパーを追加しましょう。

 module Authentication extend ActiveSupport::Concern included do before_action :require_authentication helper_method :authenticated? end class_methods do def allow_unauthenticated_access(**options) skip_before_action :require_authentication, **options end def unauthenticated_access_only(**options) allow_unauthenticated_access **options before_action -> { redirect_to root_path if authenticated? }, **options end # ... 

unauthenticated_access_onlyクラスメソッドは、アクションの利用を認証されていないユーザーのみに限定したいコントローラで利用できます。

このメソッドをSignUpsControllerの冒頭で以下のように追加できます。

 class SignUpsController < ApplicationController unauthenticated_access_only # ... end 

2.4 ユーザー登録にレート制限を追加する

このアプリケーションはインターネット上でアクセス可能になるため、悪意のあるボットやユーザーがアプリケーションにスパムの送信を試みる可能性があります。ユーザー登録にレート制限を追加して、大量のリクエストを送信するユーザーのアクセス速度を下げることが可能です。

Railsでは、コントローラ内でrate_limitメソッドを使ってレート制限を簡単に実現できます。

 class SignUpsController < ApplicationController unauthenticated_access_only rate_limit to: 10, within: 3.minutes, only: :create, with: -> { redirect_to sign_up_path, alert: "Try again later." } # ... end 

これで、ユーザー登録フォームの送信頻度が3分間あたり10回を超えると、リクエストがブロックされるようになります。

3 パスワードの編集機能

ユーザーがログインできるようになったので、ユーザーが期待する「プロフィール」「パスワード」「メールアドレス」などの設定を更新するための場所をすべて作成しましょう。

3.1 名前空間を使ったパスワードルーティング

パスワードリセット用のapp/controllers/passwords_controller.rbコントローラは、Railsの認証ジェネレータによって既に作成されています。つまり、認証済みユーザーのパスワードを編集するには、別のコントローラを使う必要があります。

この競合を防ぐために、名前空間と呼ばれる機能を利用できます。名前空間は、ルーティング、コントローラ、ビューをフォルダに整理し、2つのパスワードコントローラのような競合を防ぐのに役立ちます。

ここでは"Settings"という名前空間を作成して、ユーザーとストアの設定をアプリケーションの他の部分から分離することにします。

config/routes.rbファイルにSettings名前空間を追加し、その内側にパスワード編集用のリソースを追加します。

 namespace :settings do resource :password, only: [ :show, :update ] end 

これにより、現在のユーザーのパスワードを編集するための/settings/passwordルーティングが別途生成されます。これは、/passwordにあるパスワードリセット用のルーティングとは別物です。

3.2 名前空間化されたパスワードコントローラとビューを追加する

名前空間は、コントローラをRubyの対応するモジュールに移動します。このコントローラは、名前空間に合わせてsettings/フォルダに配置されます。

app/controllers/settings/フォルダとapp/controllers/settings/passwords_controller.rbコントローラを作成します。最初はshowアクションを作成しましょう。

 class Settings::PasswordsController < ApplicationController def show end end 

対応するビューもsettings/フォルダに移動するので、このshowアクションに対応するフォルダとビューをapp/views/settings/passwords/show.html.erbに作成しましょう。

 <h1>Password</h1> <%= form_with model: Current.user, url: settings_password_path do |form| %> <% if form.object.errors.any? %> <div><%= form.object.errors.full_messages.first %></div> <% end %> <div> <%= form.label :password_challenge %> <%= form.password_field :password_challenge, required: true, autocomplete: "current-password" %> </div> <div> <%= form.label :password %> <%= form.password_field :password, required: true, autocomplete: "new-password" %> </div> <div> <%= form.label :password_confirmation %> <%= form.password_field :password_confirmation, required: true, autocomplete: "new-password" %> </div> <div> <%= form.submit "Update password" %> </div> <% end %> 

名前空間化されたルーティングにフォームが送信されるよう、フォームでurl:引数を設定してあるので、リクエストはSettings::PasswordsControllerで処理されます。

form_withmodel: Current.user引数を渡してあるので、フォームをupdateアクションで処理するときはPATCHリクエストを送信します。

Current.userは、Active SupportのCurrentAttributesから来ています。これはリクエストごとの属性であり、各リクエストの前後で自動的にリセットされます。Railsの認証ジェネレータはこれを利用して、ログインしているユーザーをトラッキングしています。

3.3 パスワードを安全に更新する

コントローラにupdateアクションを追加しましょう。

 class Settings::PasswordsController < ApplicationController def show end def update if Current.user.update(password_params) redirect_to settings_profile_path, status: :see_other, notice: "Your password has been updated." else render :show, status: :unprocessable_entity end end private def password_params params.expect(user: [ :password, :password_confirmation, :password_challenge ]).with_defaults(password_challenge: "") end end 

セキュリティを維持するために、ユーザー本人だけがパスワードを更新できるようにする必要があります。Userモデルのhas_secure_passwordメソッドはこの属性を提供します。password_challengeフィールドが存在する場合、データベース内のユーザーの現在のパスワードと照合して一致することを確認します。

悪意のあるユーザーがブラウザでpassword_challengeフィールドそのものを削除してこのバリデーションを回避しようとする可能性があります。これを防いでバリデーションが常に実行されるようにするために、password_challengeパラメータが存在しない場合でもデフォルト値を設定するために.with_defaults(password_challenge: "")を呼び出しています。

これで、ユーザーはブラウザでhttp://localhost:3000/settings/passwordにアクセスしてパスワードを更新できるようになりました。

3.4 password_challenge属性をリネームする

password_challengeという名前はコード上では適切ですが、ユーザーにとってはこのフォームフィールドが「Current password(現在のパスワード)」と表示される方が自然です。Railsのロケールを使って、フロントエンドでのこの属性表示をリネームできます。

config/locales/en.ymlロケールファイルに以下を追加します。

 en: hello: "Hello world" products: index: title: "Products" activerecord: attributes: user: password_challenge: "Current password" 

詳しくは、国際化(I18n)ガイドを参照してください。

4 ユーザープロファイルを編集する

次は、ユーザーがプロフィールを編集できるページを追加しましょう(名前の変更など)。

4.1 プロファイル用のルーティングとコントローラ

config/routes.rbファイルを開いて、Settings名前空間の下にプロファイルリソースを追加します。名前空間にrootを追加することで、/settingsへのアクセスを処理してプロフィール設定にリダイレクトすることも可能です。

 namespace :settings do resource :password, only: [ :show, :update ] resource :profile, only: [ :show, :update ] root to: redirect("/settings/profile") end 

プロファイル編集用のコントローラをapp/controllers/settings/profiles_controller.rbに作成しましょう。

 class Settings::ProfilesController < ApplicationController def show end def update if Current.user.update(profile_params) redirect_to settings_profile_path, status: :see_other, notice: "Your profile was updated successfully." else render :show, status: :unprocessable_entity end end private def profile_params params.expect(user: [ :first_name, :last_name ]) end end 

このコントローラは、パスワードコントローラと非常に似ていますが、ユーザーのプロフィールの詳細(名前など)を更新することしかできない点が異なります。

続いて、プロファイル編集フォームを表示するapp/views/settings/profiles/show.html.erbを作成しましょう。

 <h1>Profile</h1> <%= form_with model: Current.user, url: settings_profile_path do |form| %> <% if form.object.errors.any? %> <div>Error: <%= form.object.errors.full_messages.first %></div> <% end %> <div> <%= form.label :first_name %> <%= form.text_field :first_name, required: true, autocomplete: "given-name" %> </div> <div> <%= form.label :last_name %> <%= form.text_field :last_name, required: true, autocomplete: "family-name" %> </div> <div> <%= form.submit "Update profile" %> </div> <% end %> 

これで、ブラウザでhttp://localhost:3000/settings/profileにアクセスして、プロフィールを更新できるようになりました。

4.2 更新用のリンクを更新する

更新用のリンクを更新してログアウトボタンの横に配置し、「Settings」にリンクしましょう。

app/views/layouts/application.html.erbレイアウトを開いて、ナビゲーションバーを更新します。

 <!DOCTYPE html> <html> <head> <%# ... %> </head> <body> <div class="notice"><%= flash[:notice] %></div> <div class="alert"><%= flash[:alert] %></div> <nav class="navbar"> <%= link_to "Home", root_path %> <% if authenticated? %> <%= link_to "Settings", settings_root_path %> <%= button_to "Log out", session_path, method: :delete %> <% else %> <%= link_to "Sign Up", sign_up_path %> <%= link_to "Login", new_session_path %> <% end %> </nav> 

これで、ユーザーが認証されると、ナビゲーションバーに「Settings」リンクが表示されるようになります。

4.3 「Settings」にレイアウトを追加する

ついでに、「Settings」用の新しいレイアウトを追加して、設定をサイドバーで整理できるようにしましょう。これはネステッドレイアウト(nested layout)で実現します。

ネステッドレイアウトを使うことで、アプリケーションレイアウトをレンダリングしつつ、HTML(サイドバーなど)を追加できます。これにより、「Settings」のレイアウトでヘッドタグやナビゲーションを重複させる必要がなくなります。

app/views/layouts/settings.html.erbレイアウトファイルを作成して、以下を追加します。

 <%= content_for :content do %> <section class="settings"> <nav> <h4>Account Settings</h4> <%= link_to "Profile", settings_profile_path %> <%= link_to "Password", settings_password_path %> </nav> <div> <%= yield %> </div> </section> <% end %> <%= render template: "layouts/application" %> 

「Settings」のレイアウトではサイドバー用のHTMLを提供し、アプリケーションレイアウトを親としてレンダリングするようRailsに指示しています。

そのためには、yield(:content)を使って、ネステッドレイアウトからのコンテンツをレンダリングするようにアプリケーションレイアウト(app/views/layouts/application.html.erb)を修正する必要があります。

 <!DOCTYPE html> <html> <head> <%# ... %> </head> <body> <div class="notice"><%= flash[:notice] %></div> <div class="alert"><%= flash[:alert] %></div> <nav class="navbar"> <%= link_to "Home", root_path %> <% if authenticated? %> <%= link_to "Settings", settings_root_path %> <%= button_to "Log out", session_path, method: :delete %> <% else %> <%= link_to "Sign Up", sign_up_path %> <%= link_to "Login", new_session_path %> <% end %> </nav> <main> <%= content_for?(:content) ? yield(:content) : yield %> </main> </body> </html 

これにより、アプリケーションコントローラをyieldで通常通りに利用できるようになります。ネステッドレイアウト内でcontent_for(:content)が使われている場合は、親レイアウトとしても利用できます。

2つのレイアウトの両方に<nav>タグがあるため、CSSセレクタを更新して競合を避ける必要があります。

これを行うには、app/assets/stylesheets/application.cssファイル内にあるこれらのnavセレクタに.navbarクラスを追加します。

 nav.navbar { justify-content: flex-end; display: flex; font-size: 0.875em; gap: 0.5rem; max-width: 1024px; margin: 0 auto; padding: 1rem; } nav.navbar a { display: inline-block; } 

次に、「Settings」レイアウトにサイドバー用のスタイルを設定します。

 section.settings { display: flex; gap: 1rem; } section.settings nav { width: 200px; } section.settings nav a { display: block; } 

コントローラで特定のレイアウトを指定することで、この新しいレイアウトを利用できます。layout "settings"を任意のコントローラに追加することで、レンダリングされるレイアウトを変更できます。

このレイアウトは多くのコントローラで利用されるため、設定を共有するためのベースクラスを作成して共有設定を定義し、継承を使ってそれらを利用できます。

app/controllers/settings/base_controller.rbファイルを作成し、以下を追加します。

 class Settings::BaseController < ApplicationController layout "settings" end 

次に、app/controllers/settings/passwords_controller.rbを更新して、このコントローラがベースコントローラを継承するようにします。

 class Settings::PasswordsController < Settings::BaseController 

app/controllers/settings/profiles_controller.rbも同様に更新して、ベースコントローラを継承するようにします。

 class Settings::ProfilesController < Settings::BaseController 

5 アカウントを削除する

次に、アカウントを削除する機能を追加しましょう。 まず、config/routes.rbファイルにアカウント用の別の名前空間化ルーティングを追加します。

 namespace :settings do resource :password, only: [ :show, :update ] resource :profile, only: [ :show, :update ] resource :user, only: [ :show, :destroy ] root to: redirect("/settings/profile") end 

これらの新しいルーティングを処理するために、app/controllers/settings/users_controller.rbファイルを以下の内容で作成します。

 class Settings::UsersController < Settings::BaseController def show end def destroy terminate_session Current.user.destroy redirect_to root_path, notice: "Your account has been deleted." end end 

アカウント削除用のコントローラは非常にシンプルです。showアクションでページを表示し、destroyアクションでログアウトしてユーザーを削除します。また、他のコントローラと同様にSettings::BaseControllerを継承しているため、「Settings」レイアウトが使われます。

次に、app/views/settings/users/show.html.erbファイルに以下のビューを追加します。

 <h1>Account</h1> <%= button_to "Delete my account", settings_user_path, method: :delete, data: { turbo_confirm: "Are you sure? This cannot be undone." } %> 

最後に、「Settings」レイアウト(app/views/layouts/settings.html.erb)のサイドバーにアカウントへのリンクを追加します。

 <%= content_for :content do %> <section class="settings"> <nav> <h4>Account Settings</h4> <%= link_to "Profile", settings_profile_path %> <%= link_to "Password", settings_password_path %> <%= link_to "Account", settings_user_path %> </nav> <div> <%= yield %> </div> </section> <% end %> <%= render template: "layouts/application" %> 

できました!これでアカウントを削除できるようになりました。

6 メールアドレスの更新機能を追加する

ユーザーがメールアドレスを変更する必要が生じることがあります。安全に変更を行うために、新しいメールアドレスを保存し、変更を確認するためのメールを送信する必要があります。

6.1 ユーザーによる確認が完了していないメールをusersテーブルに追加する

まず、データベースのusersテーブルに新しいフィールドを追加します。これは、確認を待っている間に新しいメールアドレスを保存するためのものです。

 $ bin/rails g migration AddUnconfirmedEmailToUsers unconfirmed_email:string 

続いて、データベースのマイグレーションを実行します。

 $ bin/rails db:migrate 

6.2 メール用のルーティングとコントローラを追加する

次に、config/routes.rbファイル内のsettings名前空間にメール用のルーティングを追加します。

 namespace :settings do resource :email, only: [ :show, :update ] resource :password, only: [ :show, :update ] resource :profile, only: [ :show, :update ] resource :user, only: [ :show, :destroy ] root to: redirect("/settings/profile") end 

次に、これを表示するためのapp/controllers/settings/emails_controller.rbファイルを以下の内容で作成します。

 class Settings::EmailsController < Settings::BaseController def show end end 

最後に、app/views/settings/emails/show.html.erbファイルに以下の内容でビューを作成します。

 <h1>Change Email</h1> <%= form_with model: Current.user, url: settings_email_path do |form| %> <% if form.object.errors.any? %> <div>Error: <%= form.object.errors.full_messages.first %></div> <% end %> <div> <%= form.label :unconfirmed_email, "New email address" %> <%= form.email_field :unconfirmed_email, required: true %> </div> <div> <%= form.label :password_challenge %> <%= form.password_field :password_challenge, required: true, autocomplete: "current-password" %> </div> <div> <%= form.submit "Update email address" %> </div> <% end %> 

処理をセキュアにするため、新しいメールアドレスをユーザーが入力したら、ユーザーの現在のパスワードをバリデーションして、アカウントの所有者だけがメールを変更できるようにする必要があります。

コントローラのupdateアクションでは、現在のパスワードをバリデーションし、新しいメールアドレスをテーブルに保存してから、新しいメールアドレスを確認するためのメールを送信します。

 class Settings::EmailsController < Settings::BaseController def show end def update if Current.user.update(email_params) UserMailer.with(user: Current.user).email_confirmation.deliver_later redirect_to settings_email_path, status: :see_other, notice: "We've sent a verification email to #{Current.user.unconfirmed_email}." else render :show, status: :unprocessable_entity end end private def email_params params.expect(user: [ :password_challenge, :unconfirmed_email ]).with_defaults(password_challenge: "") end end 

ここでは、Settings::PasswordsControllerの場合と同じwith_defaults(password_challenge: "")を使って、パスワードチャレンジのバリデーションをトリガーしています。

次に、まだ作成していなかったUserMailerメーラーを作成する必要があります。

6.3 新しいメールの確認

Railsのメーラージェネレータを使って、Settings::EmailsControllerで参照されているUserMailerを作成しましょう。

 $ bin/rails generate mailer User email_confirmation  create app/mailers/user_mailer.rb invoke erb create app/views/user_mailer create app/views/user_mailer/email_confirmation.text.erb create app/views/user_mailer/email_confirmation.html.erb invoke test_unit create test/mailers/user_mailer_test.rb create test/mailers/previews/user_mailer_preview.rb  

メール本文に含めるためのトークンを生成する必要があります。app/models/user.rbファイルを開いて、以下を追加します。

 class User < ApplicationRecord has_secure_password has_many :sessions, dependent: :destroy normalizes :email_address, with: ->(e) { e.strip.downcase } validates :first_name, :last_name, presence: true generates_token_for :email_confirmation, expires_in: 7.days do unconfirmed_email end def confirm_email update(email_address: unconfirmed_email, unconfirmed_email: nil) end def full_name "#{first_name} #{last_name}" end end 

これで、メール確認用のトークンを生成するgenerates_token_forメソッドが追加されました。このトークンには確認完了前のメールアドレスがエンコードされるため、メールアドレスが異なっていたりトークンが期限切れになった場合は無効になります。

次に、app/mailers/user_mailer.rbファイルを更新して、メール用の新しいトークンを生成しましょう。

 class UserMailer < ApplicationMailer # メールの件名は、I18n用のconfig/locales/en.ymlファイルで以下のように設定可能 # # en.user_mailer.email_confirmation.subject def email_confirmation @token = params[:user].generate_token_for(:email_confirmation) mail to: params[:user].unconfirmed_email end end 

このトークン(@token)を、app/views/user_mailer/email_confirmation.html.erbファイルのHTMLビューに含めます。

 <h1>Verify your email address</h1> <p><%= link_to "Confirm your email", email_confirmation_url(token: @token) %></p> 

app/views/user_mailer/email_confirmation.text.erbファイルにも同様にトークンを含めます。

 Confirm your email: <%= email_confirmation_url(token: @token) %> 

6.4 メール確認

確認メールには、Railsアプリへのリンクが含まれています。このリンクをクリックすると、メールアドレスの変更が確認されます。

config/routes.rbファイルで以下のようにルーティングを追加しましょう。

 namespace :email do resources :confirmations, param: :token, only: [ :show ] end 

ユーザーが確認メールのリンクをクリックすると、アプリがGETリクエストを受け取ります。このため、このコントローラで必要なのはshowアクションだけです。

次に、app/controllers/email/confirmations_controller.rbファイルを以下の内容で追加します。

 class Email::ConfirmationsController < ApplicationController allow_unauthenticated_access def show user = User.find_by_token_for(:email_confirmation, params[:token]) if user&.confirm_email flash[:notice] = "Your email has been confirmed." else flash[:alert] = "Invalid token." end redirect_to root_path end end 

メールアドレスの確認は、ユーザーが認証済みであってもなくても行えるようにしたいので、このコントローラでは認証されていないアクセスを許可しています。トークンをfind_by_token_forメソッドで検証し、Userモデル内の一致するレコードを検索します。 成功した場合は、confirm_emailメソッドを呼び出してユーザーのメールアドレスを更新し、unconfirmed_emailnilにリセットします。トークンが有効でない場合、user変数はnilになり、アラートメッセージを表示します。

最後に、「Settings」レイアウト(app/views/layouts/settings.html.erb)のサイドバーにメール送信用のリンクを追加しましょう(settings_email_path)。

 <%= content_for :content do %> <section class="settings"> <nav> <h4>Account Settings</h4> <%= link_to "Profile", settings_profile_path %> <%= link_to "Email", settings_email_path %> <%= link_to "Password", settings_password_path %> <%= link_to "Account", settings_user_path %> </nav> <div> <%= yield %> </div> </section> <% end %> <%= render template: "layouts/application" %> 

この機能を試すには、ブラウザでhttps://localhost:3000/settings/emailを開いてメールアドレスを更新します。次にメールの内容をRailsサーバーログで確認し、ブラウザで確認リンクを開いてデータベース内のメールアドレスを更新します。

7 管理者とユーザーを分離する

誰でもstoreアプリでアカウントを作成できるようになったので、通常のユーザーと管理者を区別する必要があります。

7.1 管理者フラグを追加する

まず、Userモデルにカラムを追加します。

 $ bin/rails g migration AddAdminToUsers admin:boolean 

次に、データベースをマイグレーションします。

 $ bin/rails db:migrate 

Userモデルでadmintrueに設定すると、そのユーザーはstoreアプリの管理者となり、製品の削除などの管理タスクを実行できるようになります。

7.2 Readonly属性

admin属性が悪意のあるユーザーによって編集されることのないように十分注意する必要があります。これは、:admin属性を許可されたパラメータリストから外すことで簡単に実現できます。

オプションとして、セキュリティをさらに強化するためにadmin属性を読み取り専用としてマーキングする方法もあります。これにより、admin属性が変更されるたびにRailsでエラーが発生するようになります。レコードの作成時には引き続きadmin属性を設定できますが、不正な変更に対する追加のセキュリティ層が提供されます。ユーザーのadminフラグを頻繁に変更する場合はこのオプションを導入しないことも可能ですが、このeコマースストアでは有用な保護手段です。

以下のようにUserモデルにattr_readonly属性を追加することで、属性の更新を防止できます。

 class User < ApplicationRecord has_secure_password has_many :sessions, dependent: :destroy attr_readonly :admin # ... 

admin属性を読み取り専用にすると、admin属性をActive Record経由で更新できなくなります。代わりに、データベースに対して直接SQL操作を行ってadmin属性を更新する必要があります。

Railsには、データベースに直接アクセスするためのdbconsoleコマンドがあります。これを用いて、データベースとSQLで直接対話できます。

 $ bin/rails dbconsole SQLite version 3.43.2 2023-10-10 13:08:14 Enter ".help" for usage hints. sqlite>  

表示されたSQLiteプロンプトで、レコードのadminカラムをUPDATE文で更新し、WHEREで特定のユーザーIDに絞り込めます。

 UPDATE users SET admin=true WHERE users.id=1; 

SQLiteプロンプトを閉じるには、以下のコマンドを入力します。

 .quit 

8 すべてのユーザーを表示する

storeアプリの管理者は、顧客サポートやマーケティングなどのユースケースのために、ユーザーの表示や管理機能を必要とします。

まず、config/routes.rbファイルに新しいstore名前空間でユーザーのルーティングを追加しましょう。

 # Admins Only namespace :store do resources :users end 

8.1 管理者限定のアクセスを追加する

ユーザーのコントローラへのアクセスは管理者のみに限定する必要があります。コントローラを作成する前に、管理者アクセスのみに制限するクラスメソッドを備えたAuthorizationモジュールを作成しましょう。

app/controllers/concerns/authorization.rbファイルを作成して、以下のコードを追加します。

 module Authorization extend ActiveSupport::Concern class_methods do def admin_access_only(**options) before_action -> { redirect_to root_path, alert: "You aren't allowed to do that." unless authenticated? && Current.user.admin? }, **options end end end 

作成したAuthorizationモジュールをコントローラで利用するには、app/controllers/application_controller.rbファイルで以下のようにincludeします。

 class ApplicationController < ActionController::Base include Authentication include Authorization # ... 

このAuthorizationモジュールの機能は、アプリ内の任意のコントローラで利用できます。このモジュールは、将来的に管理者や他のタイプのロールのアクセスを管理するための追加ヘルパーを配置する場所にもなります。

8.2 Usersコントローラとビューを追加する

まず、app/controllers/store/base_controller.rbファイルにstore名前空間を持つベースクラスを作成します。

 class Store::BaseController < ApplicationController admin_access_only layout "settings" end 

このコントローラへのアクセスは、先ほど作成したadmin_access_onlyメソッドによって管理者のみに制限されます。また、サイドバーを表示するときも同じ「Settings」レイアウトを利用します。

次に、app/controllers/store/users_controller.rbファイルを以下の内容で作成します。

 class Store::UsersController < Store::BaseController before_action :set_user, only: %i[ show edit update destroy ] def index @users = User.all end def show end def edit end def update if @user.update(user_params) redirect_to store_user_path(@user), status: :see_other, notice: "User has been updated" else render :edit, status: :unprocessable_entity end end def destroy end private def set_user @user = User.find(params[:id]) end def user_params params.expect(user: [ :first_name, :last_name, :email_address ]) end end 

これで、管理者はデータベース上のユーザーの一覧表示、編集、更新、削除ができるようになりました。

次に、app/views/store/users/index.html.erbファイルで以下のindexビューを作成します。

 <h1><%= pluralize @users.count, "user" %></h1> <% @users.each do |user| %> <div> <%= link_to user.full_name, store_user_path(user) %> </div> <% end %> 

次に、app/views/store/users/edit.html.erbファイルでeditビューを作成します。

 <h1>Edit User</h1> <%= render "form", user: @user %> 

フォームのパーシャルをapp/views/store/users/_form.html.erbファイルに作成します。

 <%= form_with model: [ :store, user ] do |form| %> <div> <%= form.label :first_name %> <%= form.text_field :first_name, required: true, autofocus: true, autocomplete: "given-name" %> </div> <div> <%= form.label :last_name %> <%= form.text_field :last_name, required: true, autocomplete: "family-name" %> </div> <div> <%= form.label :email_address %> <%= form.email_field :email_address, required: true, autocomplete: "email" %> </div> <div> <%= form.submit %> </div> <% end %> 

最後に、ユーザー表示用のビューをapp/views/store/users/show.html.erbファイルに作成します。

 <%= link_to "Back to all users", store_users_path %> <h1><%= @user.full_name %></h1> <p><%= @user.email_address %></p> <div> <%= link_to "Edit user", edit_store_user_path(@user) %> <%= button_to "Delete user", store_user_path(@user), method: :delete, data: { turbo_confirm: "Are you sure?" } %> </div> 

8.3 「Settings」へのリンクを追加する

次に、この管理画面へのリンクを「Settings」サイドバーのナビゲーションに追加しましょう。これは管理者にのみ表示されるべきなので、現在のユーザーが管理者であることを確認する条件でラップする必要があります。

app/views/layouts/settings.html.erbファイルの「Settings」レイアウトに以下を追加します。

 <%= content_for :content do %> <section class="settings"> <nav> <h4>Account Settings</h4> <%= link_to "Profile", settings_profile_path %> <%= link_to "Email", settings_email_path %> <%= link_to "Password", settings_password_path %> <%= link_to "Account", settings_user_path %> <% if Current.user.admin? %> <h4>Store Settings</h4> <%= link_to "Users", store_users_path %> <% end %> </nav> <div> <%= yield %> </div> </section> <% end %> <%= render template: "layouts/application" %> 

9 Productsコントローラを分離する

一般ユーザーと管理者を分離できたので、これに合わせてProductsコントローラを再編成できるようになりました。従来の単一のProductsコントローラを、公開用と管理用の2つに分割できます。

公開用のProductsコントローラはストアフロントのビューを処理し、管理用のコントローラは製品管理を担当します。

9.1 公開用のProductsコントローラ

一般向けのストアフロントでは、製品を表示するだけで十分です。つまり、app/controllers/products_controller.rbは以下のようにシンプルな形に変更できます。

 class ProductsController < ApplicationController allow_unauthenticated_access def index @products = Product.all end def show @product = Product.find(params[:id]) end end 

続いて、Productsコントローラのビューを調整しましょう。

まず、従来のProducts用ビューをstore名前空間にコピーしましょう。この名前空間でストア用の製品管理を行います。

 $ cp -R app/views/products app/views/store 

9.2 公開用のProductsビューをクリーンアップする

それでは、公開用のProductsビューから作成・更新・削除の機能をすべて削除しましょう。

app/views/products/index.html.erbファイルから"New product"へのリンクを削除します。今後は、管理者が「Settings」エリアで新しい製品を作成します。

 -<%= link_to "New product", new_product_path if authenticated? %> 

app/views/products/show.html.erbファイルから、編集と削除のリンクを削除します。

 - <% if authenticated? %> - <%= link_to "Edit", edit_product_path(@product) %> - <%= button_to "Delete", @product, method: :delete, data: { turbo_confirm: "Are you sure?" } %> - <% end %> 

以下はファイルごと削除します。

  • app/views/products/new.html.erb
  • app/views/products/edit.html.erb
  • app/views/products/_form.html.erb

9.3 管理者用のProducts CRUDを追加する

まず、config/routes.rbファイルにProductsへのルーティングをstore名前空間付きで追加しましょう。

 namespace :store do resources :products resources :users root to: redirect("/store/products") end 

続いて、app/views/layouts/settings.html.erbファイルのサイドバーにProductsの管理画面へのリンクを追加します。

 <%= content_for :content do %> <section class="settings"> <nav> <h4>Account Settings</h4> <%= link_to "Profile", settings_profile_path %> <%= link_to "Email", settings_email_path %> <%= link_to "Password", settings_password_path %> <%= link_to "Account", settings_user_path %> <% if Current.user.admin? %> <h4>Store Settings</h4> <%= link_to "Products", store_products_path %> <%= link_to "Users", store_users_path %> <% end %> </nav> <div> <%= yield %> </div> </section> <% end %> <%= render template: "layouts/application" %> 

次に、app/controllers/store/products_controller.rbファイルを以下の内容で作成します。

 class Store::ProductsController < Store::BaseController before_action :set_product, only: %i[ show edit update destroy ] def index @products = Product.all end def show end def new @product = Product.new end def create @product = Product.new(product_params) if @product.save redirect_to store_product_path(@product) else render :new, status: :unprocessable_entity end end def edit end def update if @product.update(product_params) redirect_to store_product_path(@product) else render :edit, status: :unprocessable_entity end end def destroy @product.destroy redirect_to store_products_path end private def set_product @product = Product.find(params[:id]) end def product_params params.expect(product: [ :name, :description, :featured_image, :inventory_count ]) end end 

このコントローラは、従来のProductsControllerとほぼ同じですが、2つの重要な変更点があります。

  1. admin_access_onlyを追加して、管理者ユーザーのみにアクセスを制限するようになった。
  2. リダイレクトでstore名前空間を使うことで、管理者ユーザーをストアの「Settings」に留めるようにした。

9.4 管理者用のProductsビューを更新する

管理者用のビューは、store名前空間内で動作するようにいくつかの調整が必要です。

まず、app/views/store/products/_form.html.erbform_withメソッドを、model:引数でstore名前空間を使うように更新します。

 <%= form_with model: [ :store, product ] do |form| %> <%# ... %> 

app/views/store/products/index.html.erbファイルのauthenticated?チェックを削除し、リンクもstore名前空間で更新します。

 <h1><%= t ".title" %></h1> <%= link_to "New product", new_store_product_path %> <div id="products"> <% @products.each do |product| %> <div> <%= link_to product.name, store_product_path(product) %> </div> <% end %> </div> 

このビューはstore名前空間に移動したので、見出しの<h1>タグの相対的な訳文を参照できなくなっています。これを修正するために、config/locales/en.ymlに以下の訳文を追加できます。

 en: hello: "Hello world" products: index: title: "Products" store: products: index: title: "Products" activerecord: attributes: user: password_challenge: "Current password" 

app/views/store/products/new.html.erbファイル内の"Cancel"リンクもstore名前空間で更新する必要があります。

 <h1>New product</h1> <%= render "form", product: @product %> <%= link_to "Cancel", store_products_path %> 

app/views/store/products/edit.html.erbファイルも同様に更新します。

 <h1>Edit product</h1> <%= render "form", product: @product %> <%= link_to "Cancel", store_product_path(@product) %> 

app/views/store/products/show.html.erbファイルも以下のように更新します。

 <p><%= link_to "Back", store_products_path %></p> <section class="product"> <%= image_tag @product.featured_image if @product.featured_image.attached? %> <section class="product-info"> <% cache @product do %> <h1><%= @product.name %></h1> <%= @product.description %> <% end %> <%= link_to "View in Storefront", @product %> <%= link_to "Edit", edit_store_product_path(@product) %> <%= button_to "Delete", [ :store, @product ], method: :delete, data: { turbo_confirm: "Are you sure?" } %> </section> </section> 

これで、showアクションが以下のように更新されました。

  • リンクでstore名前空間が使われるようになった。
  • "View in Storefront"リンクが追加され、管理者が製品の一般向け表示を手軽に確認できるようになった。
  • 一般向けストアフロント以外では不要なパーシャルテンプレートを削除可能になった。

管理画面では_inventory.html.erbパーシャルが不要になったため、削除しましょう。

 $ rm app/views/store/products/_inventory.html.erb 

10 テストを追加する

機能が正常に動作することを検証するため、テストをいくつか追加しましょう。

10.1 認証テストのヘルパーを追加する

このテストスイートでは、ユーザーをテスト内でサインインさせる必要があります。Railsの認証ジェネレータは認証用のヘルパーを含むように更新されていますが、認証ジェネレータがなかった時期にアプリケーションを作成した場合は、テストを書き始める前にこれらのファイルが存在することを確認しておきましょう。

test/test_helpers/session_test_helper.rbファイルには以下の内容が含まれているはずです。このファイルが存在しない場合は、ファイルを作成します。

 module SessionTestHelper def sign_in_as(user) Current.session = user.sessions.create! ActionDispatch::TestRequest.create.cookie_jar.tap do |cookie_jar| cookie_jar.signed[:session_id] = Current.session.id cookies["session_id"] = cookie_jar[:session_id] end end def sign_out Current.session&.destroy! cookies.delete("session_id") end end ActiveSupport.on_load(:action_dispatch_integration_test) do include SessionTestHelper end 

test/test_helper.rbには以下のコードがあるはずです。ない場合は追加しておきましょう。

 ENV["RAILS_ENV"] ||= "test" require_relative "../config/environment" require "rails/test_help" require_relative "test_helpers/session_test_helper" module ActiveSupport class TestCase include SessionTestHelper # ワーカー数を指定してテストを並列実行する parallelize(workers: :number_of_processors) # test/fixtures/*.yml内のすべてのフィクスチャをアルファベット順でセットアップする fixtures :all # 全テスト共通で使われるヘルパーメソッドをここに追加する end end 

10.2 ユーザー登録機能をテストする

ユーザー登録に関してテストするべきことがいくつかあります。まずは、ページを表示するためのシンプルなテストから始めましょう。

test/controllers/sign_ups_controller_test.rbファイルを以下の内容で作成します。

 require "test_helper" class SignUpsControllerTest < ActionDispatch::IntegrationTest test "view sign up" do get sign_up_path assert_response :success end end 

このテストは、/sign_upにアクセスしたときに"200 OK"レスポンスが返されることを確認します。

テストを実行してパスするかどうかを確認しましょう。

 $ bin/rails test test/controllers/sign_ups_controller_test.rb:4 Running 1 tests in a single process (parallelization threshold is 50) Run options: --seed 5967 # Running:  .  Finished in 0.559107s, 1.7886 runs/s, 1.7886 assertions/s. 1 runs, 1 assertions, 0 failures, 0 errors, 0 skips  

次に、ユーザーをサインインさせてユーザー登録ページにアクセスしてみましょう。この場合、ユーザーは既に認証済みなので、リダイレクトされるはずです。

以下のテストを同じファイルに追加します。

 test "view sign up when authenticated" do sign_in_as users(:one) get sign_up_path assert_redirected_to root_path end 

テストを再実行すると、このテストもパスするはずです。

次に、フォームに入力したときに新しいユーザーが作成されることを確認するテストを追加しましょう。

 test "successful sign up" do assert_difference "User.count" do post sign_up_path, params: { user: { first_name: "Example", last_name: "User", email_address: "example@user.org", password: "password", password_confirmation: "password" } } assert_redirected_to root_path end end 

このテストでは、createアクションをテストするためにPOSTリクエストでパラメータを送信する必要があります。

無効なデータを渡すとコントローラがエラーを返すことを確認することもテストしましょう。

 test "invalid sign up" do assert_no_difference "User.count" do post sign_up_path, params: { user: { email_address: "example@user.org", password: "password", password_confirmation: "password" } } assert_response :unprocessable_entity end end 

このテストではユーザー名を指定していないので、無効になるはずです。このリクエストは無効なので、レスポンスが"422 Unprocessable Entity"になるというアサーションが必要です。また、User.countの値が変わらないというアサーションによって、無効なリクエストでユーザーが作成されないことも確認できます。

次に追加する重要なテストは、ユーザー登録にadmin属性を渡せないことを確認するテストです。

 test "sign up ignores admin attribute" do assert_difference "User.count" do post sign_up_path, params: { user: { first_name: "Example", last_name: "User", email_address: "example@user.org", password: "password", password_confirmation: "password", admin: true } } assert_redirected_to root_path end refute User.find_by(email_address: "example@user.org").admin? end 

これは、先ほどの成功するユーザー登録と同じテストですが、admin: trueを不正に設定しようとしている点が異なります。ユーザーが作成されたというアサーションに続いて、ユーザーが管理者「ではない」というアサーションも必要です。

10.3 メールアドレスの変更機能をテストする

ユーザーのメールアドレス変更機能は複数のステップで構成されるため、これもテストしておくことが重要です。

まず、メールアドレスの更新フォームがすべて正しく処理されることを確認するためのコントローラテストを作成しましょう。

test/controllers/settings/emails_controller_test.rbファイルを以下の内容で作成します。

 require "test_helper" class Settings::EmailsControllerTest < ActionDispatch::IntegrationTest test "validates current password" do user = users(:one) sign_in_as user patch settings_email_path, params: { user: { password_challenge: "invalid", unconfirmed_email: "new@example.org" } } assert_response :unprocessable_entity assert_nil user.reload.unconfirmed_email assert_no_emails end end 

1つ目のテストは、無効なパスワードチャレンジを含むフォーム送信をテストします。ここでは、レスポンスがエラーになることと、unconfirmed_email属性が変更されていないことを確認します。メールが送信されていないこともこのテストで確認できます。

次に、フォーム送信が成功した場合のテストを作成します。

 test "sends email confirmation on successful update" do user = users(:one) sign_in_as user patch settings_email_path, params: { user: { password_challenge: "password", unconfirmed_email: "new@example.org" } } assert_response :redirect assert_equal "new@example.org", user.reload.unconfirmed_email assert_enqueued_email_with UserMailer, :email_confirmation, params: { user: user } end 

次に、test/fixtures/users.ymlのフィクスチャにファーストネームとラストネームを追加して、バリデーションがパスするようにします。

 <% password_digest = BCrypt::Password.create("password") %> one: Copilot menu email_address: one@example.com password_digest: <%= password_digest %> first_name: User last_name: One two: email_address: two@example.com password_digest: <%= password_digest %> first_name: User last_name: Two 

このテストは、有効なパラメータを送信したときに、メールアドレスがデータベースに保存されることと、ユーザーがリダイレクトされ、確認メールが配信キューに登録されることを確認します。

これらのテストを実行して、すべてのテストがパスすることを確認しましょう。

 $ bin/rails test test/controllers/settings/emails_controller_test.rb Running 2 tests in a single process (parallelization threshold is 50) Run options: --seed 31545 # Running:  ..  Finished in 0.954590s, 2.0951 runs/s, 6.2854 assertions/s. 2 runs, 6 assertions, 0 failures, 0 errors, 0 skips  

Email::ConfirmationsControllerのテストも必要です。メールアドレス更新の確認用トークンが期待通りバリデーションされ、メールアドレスの更新プロセスが正常に完了することを確認します。

test/controllers/email/confirmations_controller_test.rbファイルを以下の内容で作成します。

 require "test_helper" class Email::ConfirmationsControllerTest < ActionDispatch::IntegrationTest test "invalid tokens are ignored" do user = users(:one) previous_email = user.email_address user.update(unconfirmed_email: "new@example.org") get email_confirmation_path(token: "invalid") assert_equal "Invalid token.", flash[:alert] user.reload assert_equal previous_email, user.email_address end test "email is updated with a valid token" do user = users(:one) user.update(unconfirmed_email: "new@example.org") get email_confirmation_path(token: user.generate_token_for(:email_confirmation)) assert_equal "Your email has been confirmed.", flash[:notice] user.reload assert_equal "new@example.org", user.email_address assert_nil user.unconfirmed_email end end 

1つ目のテストは、無効なトークンでメールアドレスの変更を確認しようとするシナリオをシミュレートします。エラーメッセージが表示されるというアサーションと、メールアドレスが変更されていないというアサーションを行っています。

2つ目のテストは、有効なトークンでメールアドレスの変更を確認するシナリオをシミュレートします。成功メッセージが表示されるというアサーションと、データベース内のメールアドレスが更新されているというアサーションを行っています。

test/mailers/user_mailer_test.rbファイルを以下の内容で更新します。

 require "test_helper" class UserMailerTest < ActionMailer::TestCase test "email_confirmation" do user = users(:one) user.update(unconfirmed_email: "new@example.org") mail = UserMailer.with(user: user).email_confirmation assert_equal "Email confirmation", mail.subject assert_equal [ "new@example.org" ], mail.to assert_match "/email/confirmations/", mail.body.encoded end end 

このテストでは、ユーザーのunconfirmed_email属性が設定されていることを確認し、そのメールアドレスにメールが送信されることを確認します。メール本文に/email/confirmations/パスが含まれていることも確認します。これにより、ユーザーがクリックして新しいメールアドレスを確認するためのリンクがメールに含まれていることを確認できます。

10.4 「Settings」ナビゲーションをテストする

もう1つテストすべき領域は、設定ナビゲーションです。管理者に適切なリンクが表示され、通常のユーザーには表示されないことを確認したいと思います。

まず、test/fixtures/users.ymlのフィクスチャに管理者ユーザーを作成します。

 <% password_digest = BCrypt::Password.create("password") %> one: email_address: one@example.com password_digest: <%= password_digest %> first_name: User last_name: One two: email_address: two@example.com password_digest: <%= password_digest %> first_name: User last_name: Two admin: email_address: admin@example.com password_digest: <%= password_digest %> first_name: Admin last_name: User admin: true 

続いて、このフィクスチャを使うテストをtest/integration/settings_test.rbファイルに作成します。

 require "test_helper" class SettingsTest < ActionDispatch::IntegrationTest test "user settings nav" do sign_in_as users(:one) get settings_profile_path assert_dom "h4", "Account Settings" assert_not_dom "a", "Store Settings" end test "admin settings nav" do sign_in_as users(:admin) get settings_profile_path assert_dom "h4", "Account Settings" assert_dom "h4", "Store Settings" end end 

これらのテストによって、管理者のナビゲーションバーにだけストアの「Settings」を表示できることを確認できます。

以下のコマンドでこれらのテストを実行できます。

 $ bin/rails test test/integration/settings_test.rb 

一般ユーザーがProductsとUsersのストア「Settings」にアクセスできないことも確認しておきたいと思います。これらのテストも追加しましょう。

 test "regular user cannot access /store/products" do sign_in_as users(:one) get store_products_path assert_response :redirect assert_equal "You aren't allowed to do that.", flash[:alert] end test "regular user cannot access /store/users" do sign_in_as users(:one) get store_users_path assert_response :redirect assert_equal "You aren't allowed to do that.", flash[:alert] end 

これらのテストでは、管理者専用エリアに一般ユーザーがアクセスしようとすると、リダイレクトされてフラッシュメッセージが表示されることが確認されます。

最後に、管理者ユーザーが管理者専用エリアにアクセス可能であることを確認するテストを追加しましょう。

 test "admins can access /store/products" do sign_in_as users(:admin) get store_products_path assert_response :success end test "admins can access /store/users" do sign_in_as users(:admin) get store_users_path assert_response :success end 

テストを再実行して、すべてのテストがパスすることを確認します。

 $ bin/rails test test/integration/settings_test.rb Running 6 tests in a single process (parallelization threshold is 50) Run options: --seed 33354 # Running:  ......  Finished in 0.625542s, 9.5917 runs/s, 12.7889 assertions/s. 6 runs, 8 assertions, 0 failures, 0 errors, 0 skips  

最後にテストスイート全体をもう一度実行して、すべてのテストがパスすることを確認します。

 $ bin/rails test Running 18 tests in a single process (parallelization threshold is 50) Run options: --seed 38561 # Running:  ..................  Finished in 0.915621s, 19.6588 runs/s, 51.3313 assertions/s. 18 runs, 47 assertions, 0 failures, 0 errors, 0 skips  

素晴らしい!それではこれをproduction環境にデプロイしましょう。

11 production環境にデプロイする

RailsをはじめようガイドでKamalをセットアップしているので、コードの変更をGitリポジトリにプッシュして、以下のコマンドを実行するだけでデプロイは完了します。

 $ bin/kamal deploy 

これで、storeアプリケーションの新しいコンテナがビルドされ、productionサーバーにデプロイされます。

11.1 production環境で管理者アカウントを設定する

Userattr_readonly :adminを追加した場合は、以下のようにdbconsoleでアカウントを更新する必要があります。

 $ bin/kamal dbc UPDATE users SET admin=true WHERE users.email='you@example.org'; .quit  

あるいは、以下のようにRailsコンソールでアカウントを更新することも可能です。

 $ bin/kamal console irb> User.find_by(email: "you@example.org").update(admin: true) 

これで、このアカウントを使ってproduction環境でStoreの「Settings」にアクセスできるようになりました。

12 今後の機能

以上ですべて完了しました!eコマースストアで「ユーザー登録」「アカウント管理」「製品とユーザーの管理用の管理者エリア」がサポートされるようになりました。

次は、演習: ウィッシュリスト機能を追加するに従って学習を続けてください。

Happy building!

全レベルユーザー向けのチュートリアル紹介ページ(英語)に戻る

13 関連リンク

フィードバックについて

Railsガイドは GitHub の yasslab/railsguides.jp で管理・公開されております。本ガイドを読んで気になる文章や間違ったコードを見かけたら、気軽に Pull Request を出して頂けると嬉しいです。Pull Request の送り方については GitHub の README をご参照ください。

原著における間違いを見つけたら『Rails のドキュメントに貢献する』を参考にしながらぜひ Rails コミュニティに貢献してみてください 🛠💨✨

本ガイドの品質向上に向けて、皆さまのご協力が得られれば嬉しいです。

Railsガイド運営チーム (@RailsGuidesJP)

支援・協賛

Railsガイドは下記の協賛企業から継続的な支援を受けています。支援・協賛にご興味あれば協賛プランからお問い合わせいただけると嬉しいです。

  1. Star
  2. このエントリーをはてなブックマークに追加