ユーザーの認証周りの細かいバリデーション


これは何?

認証周りは慎重に記述しなければならない領域のため、

テスト含めて一度ベースとなるバリデーション周りを整理する。


環境

Ruby 2.5.1

Rails 5.2

RSpec 3.8


ログインバリデーション

bundle exec rails g model User name:string email:string


app/models/user.rb

class User < ApplicationRecord

before_save { self.email = self.email.downcase }

validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX }, uniquneness: { case_sensitive: false }
has_secure_password
validates :password, presence: true, length: { minimum: 6 }
end



spec/factories/users.rb

FactoryBot.define do

factory :user do
name { "Jo Aoki" }
sequence(:email) { |n| "jo#{n}@reactcorp.com" }
password { "password" }
end
end


spec/models/user_spec.rb

require 'rails_helper'

RSpec.describe User, type: :model do
before do
user_params = FactoryBot.attributes_for(:user)
@user = User.new(user_params)
end
it "should be valid" do
expect(@user).to be_valid
end
it "name should be present" do
@user.name = ""
expect(@user).not_to be_valid
end
it "name should be proper long" do
@user.name = "a"*50
expect(@user).to be_valid
end
it "name should not be too long" do
@user.name = "a"*51
expect(@user).not_to be_valid
end
it "email should be present" do
@user.email = ""
expect(@user).not_to be_valid
end
it "email should be proper long" do
@user.email = "a"*243+"@example.com"
expect(@user).to be_valid
end
it "email should not be too long" do
@user.email = "a"*244 + "@example.com"
expect(@user).not_to be_valid
end
it "email should be valid format" do
valid_addresses = %w[user@example.com user_123@foo.jp USER.111@bar.com]
valid_addresses.each do |valid_address|
user = FactoryBot.build(:user, email: valid_address)
expect(user).to be_valid
end
end
it "email should not be invalid format" do
invalid_addresses = %w[user@example,com user@example abcd_user@example.com.jp user@example_com user@example..com]
invalid_addresses.each do |invalid_address|
user = FactoryBot.build(:user, email: invalid_address)
expect(user).not_to be_valid
end
end
it "email addresses should be unique" do
duplicate_user = FactoryBot.build(:user, email:@user.email)
@user.save
expect(duplicate_user).not_to be_valid
end
it "email addresses should be alphabetically unique" do
user = FactoryBot.create(:user, email: "JO@reactCORP.COM")
duplicate_user = FactoryBot.create(:user, email: user.email.downcase)
expect(duplicate_user).not_to be_valid
end
it "saves only downcase email" do
user = FactoryBot.create(:user, email: "JO@reaCTCorp.coM"
expect(user.reload.email).to eq "jo@reactcorp.com"
end
it "password should be present" do
user = FactoryBot.build(:user, password: ""*10)
expect(user).not_to be_valid
end
it "is valid with a proper length password" do
user = FactoryBot.build(:user, password: "a"*6)
expect(user).to be_valid
end
 it "is invalid with a short length password" do
user = FactoryBot.build(:user, password: "a"*5)
expect(user).not_to be_valid
end
end



注意点


▼ uniquenessの欠点

一意性のバリデーションをかけていたとしても、

ユーザーが二度連続で保存ボタンを押してしまった場合などはuniquenessのバリデーションが効きません。

その解決策としてDBレベルでのuniquenessを担保していれば解決します。

bundle exec rails g migration add_index_to_users_email


db/migrate/hogehoge.rb

class AddIndexToUsersEmail < ActiveRecord::Migration[5.2]

def change
add_index :users, :email, unique: true
end
end

bundle exec rails db:migrate

これにより、uniquenessの解決もされますが、emailカラムにindexが貼られたことになる為、

emailによる検索速度が向上するという副次的なプラスもあります。


▼ has_secure_passwordの効果


  • DBのpassword_digestにハッシュ化したパスワードを保存する


    • 事前にstring型のカラムを用意する必要がある



  • 仮想属性(passwordpassword_confirmation)を作成する


    • 両者が一致しているかどうかの確認もする

    • 存在確認は一応してくれるが、更新時にはバリデーションを通らないので、必ず存在確認のバリデーションを入れる。




  • authenticate(params[:password])メソッドが利用可能


    • 引数のパスワードが一致すればUserオブジェクトを、間違えるとfalseを返す



bundle exec rails g migration add_password_digest_to_users password_digest:string

bundle exec rails db:migrate

ハッシュ化のためにbcryptgemを利用します。