これは何?
認証周りは慎重に記述しなければならない領域のため、
テスト含めて一度ベースとなるバリデーション周りを整理する。
環境
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型のカラムを用意する必要がある
- 仮想属性(
password
とpassword_confirmation
)を作成する- 両者が一致しているかどうかの確認もする
- 存在確認は一応してくれるが、更新時にはバリデーションを通らないので、必ず存在確認のバリデーションを入れる。
-
authenticate(params[:password])
メソッドが利用可能- 引数のパスワードが一致すればUserオブジェクトを、間違えるとfalseを返す
bundle exec rails g migration add_password_digest_to_users password_digest:string
bundle exec rails db:migrate
ハッシュ化のためにbcrypt
gemを利用します。