※この記事はRails初学者向けです
deviceってなに?
railsのgemの一つで、ログイン認証機能が簡単に実装できる。
https://github.com/heartcombo/devise
gemは便利だけど、勉強のためにログイン認証周りの仕組みに興味があったので実装してみました。view周りの実装は今回の趣旨とは異なるため触れてません。
モデル・コントローラの準備
1. Userモデルを作成して、schemaファイルを修正
$ rails g model user name:string email:string encrypted_password:string
invoke active_record
create db/migrate/[timestamp]_create_users.rb
create app/models/user.rb
invoke rspec
create spec/models/user_spec.rb
- db/migrate/[timestamp]_create_users.rb に追記
class CreateUsers < ActiveRecord::Migration[7.0]
def change
create_table :users do |t|
t.string :name, null: false
t.string :email, null: false
t.string :encrypted_password, null: false
t.timestamps
t.index :email, unique: true
end
end
end
ログイン時にemailからユーザを特定するため、indexを貼って検索速度を上げ、
unique: true
としてメールアドレスの一意性が保たれるようにします。
追記できたら以下を実行
$ rails db:migrate
== [timestamp] CreateUsers: migrating ==================================
-- create_table(:users)
-> 0.0420s
== [timestamp] CreateUsers: migrated (0.0421s) =========================
$ rails db:migrate:redo
== [timestamp] CreateUsers: reverting ==================================
-- drop_table(:users)
-> 0.0083s
== [timestamp] CreateUsers: reverted (0.0176s) =========================
== [timestamp] CreateUsers: migrating ==================================
-- create_table(:users)
-> 0.0140s
== [timestamp] CreateUsers: migrated (0.0141s) =========================
マイグレーションは1つ前の状態に戻せることを担保できていることが大切なので
rails db:migrate:redo
を流して確認する癖をつけましょう。
2. Userの新規登録機能の実装
Usersコントローラを作成
$ rails g controller Users new create
create app/controllers/users_controller.rb
route get 'users/new'
get 'users/create'
(省略)
ルーティングを修正
- config/routes.rbに追記
Rails.application.routes.draw do
(省略)
resources :users, only: [:new, :create]
end
Userモデルにバリデーションやコールバックを実装
- app/models/user.rbに追記
class User < ApplicationRecord
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
attribute :password, :string
validates :name, presence: true, length: { maximum: 50 }
validates :email, presence: true, length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
validates :encrypted_password, presence: true, length: { is: 128 }
validates :password, presence: true, length: { minimum: 6 }
after_initialize do |user|
user.encrypted_password = Digest::SHA512.hexdigest(user.password) if user.password
end
before_save :downcase_email!
private
def downcase_email!
email.downcase!
end
end
after_initialize
コールバックの中で、ユーザ登録される際、view側からpassword
として受け取り、それをハッシュ化(データを不規則な文字列に変換する手法)してencrypted_password
に渡しています。
http://localhost:3000/users/new にアクセスし、
ユーザが登録できることを確認しましょう。
$ rails c
Loading development environment (Rails 7.0.3.1)
irb(main):001:0> User.all
User Load (8.4ms) SELECT `users`.* FROM `users`
=>
[#<User:0x000000011668b598
id: 1,
name: "test_user",
email: "test@example.com",
encrypted_password: "[FILTERED]",
(省略)
登録できていそうですね。
次はログインログアウトの機能を追加していきます。
3. ログイン・ログアウト機能の実装
sessionsコントローラを作成します。
$ rails g controller Sessions new create destroy
create app/controllers/sessions_controller.rb
route get 'sessions/new'
get 'sessions/create'
get 'sessions/destroy'
(省略)
ルーティングを修正
- config/routes.rbに追記
Rails.application.routes.draw do
(省略)
resources :users, only: [:new, :create]
get :login, to: 'sessions#new'
post :login, to: 'sessions#create'
delete :logout, to: 'sessions#destroy'
end
pathを分かりやすくするために login
とlogout
に設定しました。
続いてコントローラの中身を実装していきたいのですが、その前にControllerやviewで使いたいので、ヘルパーを準備しておきます。
- app/helpers/sessions_helper.rb に追記
module SessionsHelper
def log_in(user)
session[:user_id] = user.id
end
def current_user
@current_user ||= User.find_by(id: session[:user_id]) if session[:user_id]
end
def log_out
reset_session
end
end
sessions_helperをapplication_controllerでincludeしておきます。
- app/controllers/application_controller.rbに追記
class ApplicationController < ActionController::Base
include SessionsHelper
end
- app/controllers/sessions_controller.rbに追記
class SessionsController < ApplicationController
def new
end
def create
user = User.find_by(email: params[:email].downcase)
if user && Digest::SHA512.hexdigest(params[:password]) == user.encrypted_password
login
flash[:success] = 'ログインが成功しました'
redirect_to root_path
else
flash[:danger] = 'メールアドレス・またはパスワードが正しくありません'
render :new
end
end
def destroy
reset_session
redirect_to root_path
end
end
ここでやってるのは、先にUserモデルからparams[:email]
に一致するユーザを検索。ユーザが見つかり、かつuser.encrypted_password
とparams[:password]
をハッシュ化した値が一致すればsession[:user_id]
にuser_id
を入れています。
http://localhost:3000/login にアクセスしログインしてみましょう。
これでログインできました。
ログイン状態ならログアウトボタン、ログアウト状態ならユーザ登録とログインボタンを表示させてみましょう。
今回はテスト用にヘッダーに表示させています。
- app/views/layouts/_header.html.erbに追記
<header class="navbar navbar-fixed-top">
<div class="container">
<%= link_to(image_tag("", :alt => ""), root_url, id: 'logo') %>
<nav>
<ul class="nav navbar-nav navbar-left">
<% if current_user %>
<li class="navbar-item"><%= button_to "ログアウト", logout_path, method: :delete %></li>
<% else %>
<li class="navbar-item"><%= link_to "ユーザー登録", new_user_path %></li>
<li class="navbar-item"><%= link_to "ログイン", login_path %></li>
<% end %>
</ul>
</nav>
</div>
</header>
うまく実装できていそうです。
current_user
でログイン状態が分かるようになったので、別の機能の実装する際も、ログインしていなければリダイレクトさせるようなコールバックにも使えますね。
gemを使わずにログイン周りの機能を実装できました。
これで以上になります!