はじめに
本 Chapter では、devise を使った認証機能の実装を行います。
管理者・顧客の認証機能を作っていきましょう。
認証画面については次の Chapter で行います。
devise のセットアップ
最初に行うのは devise のセットアップです。
下記コマンドを実行して、コンテナが立ち上がっていることを確認してください。
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
4121e2a29d55 ecommerce_cable "puma -p 28080 cable…" 2 days ago Up 12 hours 8000/tcp, 0.0.0.0:28080->28080/tcp ecommerce_cable_1
a6427279ff7d ecommerce_worker "bundle exec sidekiq…" 2 days ago Up 12 hours 8000/tcp ecommerce_worker_1
af555970e305 ecommerce_web "/app/bin/docker-ent…" 2 days ago Up 12 hours (healthy) 0.0.0.0:8000->8000/tcp ecommerce_web_1
4bfcc2ecd604 ecommerce_js "yarn build" 2 days ago Up 12 hours ecommerce_js_1
d2f818dc734a ecommerce_css "yarn build:css" 2 days ago Up 12 hours ecommerce_css_1
80d68a169888 postgres:15.0-bullseye "docker-entrypoint.s…" 2 days ago Up 12 hours 5432/tcp ecommerce_postgres_1
85ff5e841998 redis:7.0.5-bullseye "docker-entrypoint.s…" 2 days ago Up 12 hours 6379/tcp ecommerce_redis_1
コンテナが立ち上がっていることが確認できましたら、コンテナ内に入りましょう。
$ docker-compose run --rm web bash
下記のように、app フォルダにログインできたら OK です。
ここで、rails のコマンドを実行することができます。
Creating ecommerce_web_run ... done
ruby@c5f61373fefc:/app$
最初に、下記コマンドを実行して、devise の設定ファイル等を Rails アプリケーションにインストールしましょう。
$ rails g devise:install
コマンドを実行した結果、下記のようなログが表示されていれば OK です。
create config/initializers/devise.rb
create config/locales/devise.en.yml
===============================================================================
Depending on your application's configuration some manual setup may be required:
1. Ensure you have defined default url options in your environments files. Here
is an example of default_url_options appropriate for a development environment
in config/environments/development.rb:
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
In production, :host should be set to the actual host of your application.
* Required for all applications. *
2. Ensure you have defined root_url to *something* in your config/routes.rb.
For example:
root to: "home#index"
* Not required for API-only Applications *
3. Ensure you have flash messages in app/views/layouts/application.html.erb.
For example:
<p class="notice"><%= notice %></p>
<p class="alert"><%= alert %></p>
* Not required for API-only Applications *
4. You can copy Devise views (for customization) to your app by running:
rails g devise:views
* Not required *
===============================================================================
上記ログに表示されているセットアップは後ほど行います。
モデル・テーブルの作成
続いて、Admin モデルと Customer モデルを作成し、それぞれのテーブルを作成します。
下記二つのコマンドを実行してください。
$ rails g devise Admin
$ rails g devise Customer
実行が完了しましたら、customers テーブルを作成するためのマイグレーションファイルを開いてください。
customers テーブルには、メールアドレスとパスワードを格納するカラムの他に、名前とステータスを格納するカラムを持たせます。よって、下記コードを追加しましょう。
# frozen_string_literal: true
class DeviseCreateCustomers < ActiveRecord::Migration[7.0]
def change
create_table :customers do |t|
## Database authenticatable
t.string :email, null: false, default: ""
t.string :encrypted_password, null: false, default: ""
## Recoverable
t.string :reset_password_token
t.datetime :reset_password_sent_at
## Rememberable
t.datetime :remember_created_at
## Trackable
# t.integer :sign_in_count, default: 0, null: false
# t.datetime :current_sign_in_at
# t.datetime :last_sign_in_at
# t.string :current_sign_in_ip
# t.string :last_sign_in_ip
## Confirmable
# t.string :confirmation_token
# t.datetime :confirmed_at
# t.datetime :confirmation_sent_at
# t.string :unconfirmed_email # Only if using reconfirmable
## Lockable
# t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
# t.string :unlock_token # Only if unlock strategy is :email or :both
# t.datetime :locked_at
+ t.string :name, null: false
+ t.integer :status, null: false, default: 0
t.timestamps null: false
end
add_index :customers, :email, unique: true
add_index :customers, :reset_password_token, unique: true
# add_index :customers, :confirmation_token, unique: true
# add_index :customers, :unlock_token, unique: true
end
end
ステータスは enum で管理しますので、integer 型であることに注意です。
それでは、admins テーブルと customers テーブルを作成するためにマイグレーションを実行します。
$ rails db:migrate
マイグレーション実行後、db/schema.rb
に下記二つのテーブルが追加されていれば OK です。
customers テーブルには、デフォルトで生成されるカラムに加えて、name
と status
が追加されていますね。
create_table "admins", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["email"], name: "index_admins_on_email", unique: true
t.index ["reset_password_token"], name: "index_admins_on_reset_password_token", unique: true
end
create_table "customers", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.string "name", null: false
t.integer "status", default: 0, null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["email"], name: "index_customers_on_email", unique: true
t.index ["reset_password_token"], name: "index_customers_on_reset_password_token", unique: true
end
次に、customer モデルにバリデーションを設定しておきます。
app/models/customer.rb
を開いて下記コードを記述してください。
class Customer < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
+ with_options presence: true do
+ validates :name
+ validates :status
+ end
end
上記の記述により、新規登録やアカウントを編集する際に、name と status が空の場合にバリデーションではじくことができます。
続いて、customer モデルに enum の設定を行いましょう。
enum(enumeration: 列挙)とは、名前を整数の定数に割り当てるのに使われるデータ型です。名前は言語の定数として振る舞う識別子なので、整数を直に扱う場合よりもプログラムの読みやすさとメンテナンス性が向上します。
それでは、app/models/customer.rb
に下記コードを記述してください。
class Customer < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
with_options presence: true do
validates :name
validates :status
end
+ enum status: {
+ normal: 0,
+ withdrawn: 1,
+ banned: 2
+ }
end
上記の記述により、データベースには 0 や 1 といった数値で status が保存されますが、rails console から値を取得すると、数値ではなくて enum で定義した normal や withdrawn といった文字列で取り出すことができるようになります。
今回、status カラムは
- normal(通常)
- withdrawn(退会済み)
- banned(停止)
のいずれかの状態を表します。
コントローラ・ビューの作成
次に、管理者・顧客の認証・認可を処理するコントローラを作成します。
下記コマンドを実行して、必要なコントローラを作成しましょう。
$ rails g devise:controllers admin
$ rails g devise:controllers customer
コマンド実行後に、admin ディレクトリ内・customer ディレクトリ内にコントローラが生成されていることを確認してください。
├── controllers
│ ├── admin
│ │ ├── confirmations_controller.rb
│ │ ├── omniauth_callbacks_controller.rb
│ │ ├── passwords_controller.rb
│ │ ├── registrations_controller.rb
│ │ ├── sessions_controller.rb
│ │ └── unlocks_controller.rb
│ ├── application_controller.rb
│ ├── concerns
│ ├── customer
│ │ ├── confirmations_controller.rb
│ │ ├── omniauth_callbacks_controller.rb
│ │ ├── passwords_controller.rb
│ │ ├── registrations_controller.rb
│ │ ├── sessions_controller.rb
│ │ └── unlocks_controller.rb
│ ├── pages_controller.rb
│ └── up_controller.rb
続いて、認証に関する画面を作成します。
下記コマンドを実行してビューを生成してください。
$ rails g devise:views admins
$ rails g devise:views customers
コマンド実行後、下記のようにビューが生成されていれば OK です。
└── views
├── admins
│ ├── confirmations
│ │ └── new.html.erb
│ ├── mailer
│ │ ├── confirmation_instructions.html.erb
│ │ ├── email_changed.html.erb
│ │ ├── password_change.html.erb
│ │ ├── reset_password_instructions.html.erb
│ │ └── unlock_instructions.html.erb
│ ├── passwords
│ │ ├── edit.html.erb
│ │ └── new.html.erb
│ ├── registrations
│ │ ├── edit.html.erb
│ │ └── new.html.erb
│ ├── sessions
│ │ └── new.html.erb
│ ├── shared
│ │ ├── _error_messages.html.erb
│ │ └── _links.html.erb
│ └── unlocks
│ └── new.html.erb
├── customers
│ ├── confirmations
│ │ └── new.html.erb
│ ├── mailer
│ │ ├── confirmation_instructions.html.erb
│ │ ├── email_changed.html.erb
│ │ ├── password_change.html.erb
│ │ ├── reset_password_instructions.html.erb
│ │ └── unlock_instructions.html.erb
│ ├── passwords
│ │ ├── edit.html.erb
│ │ └── new.html.erb
│ ├── registrations
│ │ ├── edit.html.erb
│ │ └── new.html.erb
│ ├── sessions
│ │ └── new.html.erb
│ ├── shared
│ │ ├── _error_messages.html.erb
│ │ └── _links.html.erb
│ └── unlocks
│ └── new.html.erb
├── layouts
│ ├── application.html.erb
│ ├── mailer.html.erb
│ └── mailer.text.erb
└── pages
└── home.html.erb
さて、もうお気づきの方もおられると思いますが、コントローラとビューの名前空間が一致していません。
コントローラは admin・customer ですが、ビューは admins・customers となっています。
例えば、app/controllers/admin/sessions_controller.rb
の中身を見てみると次のような記述があるはずです。
class Admin::SessionsController < Devise::SessionsController
Admin::SessionsController
の ::
の左側に記載されている Admin
が名前空間です。
今回は名前空間をコントローラに合わせることにしましょう。
よって、views フォルダ配下に生成された admins を admin に、customers を customer に修正してください。
└── views
├── admin
│ ├── confirmations
│ │ └── new.html.erb
│ ├── mailer
│ │ ├── confirmation_instructions.html.erb
│ │ ├── email_changed.html.erb
│ │ ├── password_change.html.erb
│ │ ├── reset_password_instructions.html.erb
│ │ └── unlock_instructions.html.erb
│ ├── passwords
│ │ ├── edit.html.erb
│ │ └── new.html.erb
│ ├── registrations
│ │ ├── edit.html.erb
│ │ └── new.html.erb
│ ├── sessions
│ │ └── new.html.erb
│ ├── shared
│ │ ├── _error_messages.html.erb
│ │ └── _links.html.erb
│ └── unlocks
│ └── new.html.erb
├── customer
│ ├── confirmations
│ │ └── new.html.erb
│ ├── mailer
│ │ ├── confirmation_instructions.html.erb
│ │ ├── email_changed.html.erb
│ │ ├── password_change.html.erb
│ │ ├── reset_password_instructions.html.erb
│ │ └── unlock_instructions.html.erb
│ ├── passwords
│ │ ├── edit.html.erb
│ │ └── new.html.erb
│ ├── registrations
│ │ ├── edit.html.erb
│ │ └── new.html.erb
│ ├── sessions
│ │ └── new.html.erb
│ ├── shared
│ │ ├── _error_messages.html.erb
│ │ └── _links.html.erb
│ └── unlocks
│ └── new.html.erb
├── layouts
│ ├── application.html.erb
│ ├── mailer.html.erb
│ └── mailer.text.erb
└── pages
└── home.html.erb
また、それに伴い、ビューファイルに記述されている部分テンプレートのパスも修正する必要があります。部分テンプレートを呼び出している部分は、下記のような修正を加えてください。
- <%= render "admins/shared/links" %>
+ <%= render "admin/shared/links" %>
- <%= render "admins/shared/error_messages", resource: resource %>
+ <%= render "admin/shared/error_messages", resource: resource %>
- <%= render "customers/shared/links" %>
+ <%= render "customer/shared/links" %>
- <%= render "customers/shared/error_messages", resource: resource %>
+ <%= render "customer/shared/error_messages", resource: resource %>
テキストエディタとして Visual Studio Code を使っている方でしたら、一気に置換する機能を使いましょう。
これで、コントローラとビューの名前空間の整合性がとれました。
(なぜ最初からビューのディレクトリ名を単数系にしなかったのかというと、devise の仕様上、単数系で作成しようとしても、複数形のディレクトリ名になってしまうためです。)
ルーティングの設定
続いて、ルーティングの設定を行なっていきます。
今回のアプリケーションでは、
- 管理者はログイン・ログアウト
- 顧客は新規登録・アカウント情報編集・ログイン・ログアウト
といった機能しか実装しません。したがって、ルーティングの設定は最低限にしておきます。
下記コードを追加してください。
Rails.application.routes.draw do
+ devise_for :admins, controllers: {
+ sessions: 'admin/sessions'
+ }
+ devise_for :customers, controllers: {
+ sessions: 'customer/sessions',
+ registrations: 'customer/registrations'
+ }
root to: 'pages#home'
get '/up/', to: 'up#index', as: :up
get '/up/databases', to: 'up#databases', as: :up_databases
end
念の為、どんな URL が生成されたか確認しましょう。
下記コマンドを実行してください。
$ rails routes | grep -e admin/ -e customer/
grep を用いて表示するルーティングを絞っています。
new_admin_session GET /admins/sign_in(.:format) admin/sessions#new
admin_session POST /admins/sign_in(.:format) admin/sessions#create
destroy_admin_session DELETE /admins/sign_out(.:format) admin/sessions#destroy
new_customer_session GET /customers/sign_in(.:format) customer/sessions#new
customer_session POST /customers/sign_in(.:format) customer/sessions#create
destroy_customer_session DELETE /customers/sign_out(.:format) customer/sessions#destroy
cancel_customer_registration GET /customers/cancel(.:format) customer/egistrations#cancel
new_customer_registration GET /customers/sign_up(.:format) customer/egistrations#new
edit_customer_registration GET /customers/edit(.:format) customer/egistrations#edit
customer_registration PATCH /customers(.:format) customer/egistrations#update
PUT /customers(.:format) customer/egistrations#update
DELETE /customers(.:format) customer/egistrations#destroy
POST /customers(.:format) customer/egistrations#create
上記のように、想定通りの URL とコントローラが指定されていますね。
認証機能の設定
続いて、実際に新規登録やログインといった認証機能に関する設定を行なっていきます。
まずは、顧客が新規登録・アカウントの編集する際に、name をコントローラ側に送信できるように、パラメータの許可を行います。
app/controllers/customer/registrations_controller.rb
を開いて、下記コードを記述してください。
# frozen_string_literal: true
class Customer::RegistrationsController < Devise::RegistrationsController
+ before_action :configure_sign_up_params, only: [:create]
+ before_action :configure_account_update_params, only: [:update]
# GET /resource/sign_up
# def new
# super
# end
# POST /resource
# def create
# super
# end
# GET /resource/edit
# def edit
# super
# end
# PUT /resource
# def update
# super
# end
# DELETE /resource
# def destroy
# super
# end
# GET /resource/cancel
# Forces the session data which is usually expired after sign
# in to be expired now. This is useful if the user wants to
# cancel oauth signing in/up in the middle of the process,
# removing all OAuth session data.
# def cancel
# super
# end
+ protected
# If you have extra params to permit, append them to the sanitizer.
+ def configure_sign_up_params
+ devise_parameter_sanitizer.permit(:sign_up, keys: [:name])
+ end
# If you have extra params to permit, append them to the sanitizer.
+ def configure_account_update_params
+ devise_parameter_sanitizer.permit(:account_update, keys: [:name])
+ end
# The path used after sign up.
# def after_sign_up_path_for(resource)
# super(resource)
# end
# The path used after sign up for inactive accounts.
# def after_inactive_sign_up_path_for(resource)
# super(resource)
# end
end
configure_sign_up_params
メソッドは create アクションが呼ばれた時、configure_account_update_params
メソッドは update アクションが呼ばれた時に実行されます。
どちらのメソッドも name パラメータを許可しています。
次に、ログアウトの scope を変更します。
今回、管理者と顧客の認証機能に対して devise を使っているわけですが、デフォルトですと、どちらかがログアウトしたら両方のセッションが切れるという仕様になっています。
「管理者がログアウトしても、顧客のログイン状態は保ちたい」、また、「顧客がログアウトしても、管理者のログイン状態は保ちたい」ため、ログアウトの scope を変更しましょう。
config/initializers/devise.rb
内の config.sign_out_all_scopes
を false
にしてください。(255行目あたりにあるはずです。)
# Set this configuration to false if you want /users/sign_out to sign out
# only the current scope. By default, Devise signs out all scopes.
config.sign_out_all_scopes = false
ついでに、config.scoped_views
も編集しておきましょう。
config.scoped_views
を true
にすることで認証画面のレンダリングが早くなります。
# Turn scoped views on. Before rendering "sessions/new", it will first check for
# "users/sessions/new". It's turned off by default because it's slower if you
# are using only default views.
config.scoped_views = true
これで、認証機能に関する設定は完了です。
最後に、RuboCop を実行し、必要であればコードを修正しましょう。
$ rubocop
今回は、RuboCop の静的解析を全て PASS したようです。
Inspecting 45 files
.............................................
45 files inspected, no offenses detected
もしも指摘事項があれば修正してください。
コミットしておきましょう。
$ git add . && git commit -m "Implementing Authentication Functions with devise"
おわりに
お疲れ様でした。
本 Chapter では、認証機能に関する実装を行いました。
認証機能自体は完成しています。
次の Chapter では、新規登録画面やログイン画面の UI を作成します。