##アカウントを有効化する
今度はAccountActivationsコントローラのeditアクションを書いていきましょう。
AccountActivationsコントローラからUserモデルにコードを移していく作業(リファクタリング)にも取り掛かっていきます。
###authenticated?メソッドの抽象化
$ rails console
>> a = [1, 2, 3]
>> a.length
=> 3
>> a.send(:length)
=> 3
>> a.send("length")
=> 3
シンボルも文字列もlengthメソッドなので等価になる。
>> user.activation_digest
=> "$2a$12$x4xVHmtrDFjVPFFWav31UebLY8r9sdT8ImzSXGSrnqtQBKHyw8Ff."
>> user.send(:activation_digest)
=> "$2a$12$x4xVHmtrDFjVPFFWav31UebLY8r9sdT8ImzSXGSrnqtQBKHyw8Ff."
>> user.send("activation_digest")
=> "$2a$12$x4xVHmtrDFjVPFFWav31UebLY8r9sdT8ImzSXGSrnqtQBKHyw8Ff."
>> attribute = :activation
=> :activation
# Rubyではシンボルを使う方が一般的
>> user.send("#{attribute}_digest")
=> "$2a$12$x4xVHmtrDFjVPFFWav31UebLY8r9sdT8ImzSXGSrnqtQBKHyw8Ff."
class User < ApplicationRecord
attr_accessor :remember_token, :activation_token
before_save :downcase_email
before_create :create_activation_digest
# トークンとそれに対応するダイジェストを割り当てる
validates :name, presence: true, length: { maximum: 50 }
#属性はname,属性の存在を検証、 最大50字まで
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 },
#最大255字まで
format: { with: VALID_EMAIL_REGEX },
uniqueness: true
#???
has_secure_password
#セキュアなパスワードの実装
# オブジェクト生成時に存在性を検証
# 空のパスワード(nil)が新規ユーザー登録時に有効になることはありません
validates :password, presence: true, length: { minimum: 5 }, allow_nil: true
# パスワードのバリデーションの設定
# 最低は六文字
# パスワードのバリデーションに対して、空だったときの例外処理を加える
def self.digest(string)
# 渡された文字列のハッシュ値を返す
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
.
.
.
# 渡されたトークンがダイジェストと一致したらtrueを返す
def authenticated?
# アカウント有効化のダイジェストと、
# 渡されたトークンが一致するかどうかをチェック
# 記憶トークン用なので今は正常に動作しません。
digest = send("#{attribute}_digest")
# rememberの部分を変数にして、状況に応じて呼び出すメソッドを切り替える
return false if digest.nil?
BCrypt::Password.new(digest).is_password?(token)
end
.
.
.
end
#####テスト
ubuntu:~/environment/sample_app (account-activation) $ rails t
Running via Spring preloader in process 6707
Started with run options --seed 3172
ERROR["test_current_user_returns_nil_when_remember_digest_is_wrong", #<Minitest::Reporters::Suite:0x0000563b1d765260 @name="SessionsHelperTest">, 2.9934008690001974]
test_current_user_returns_nil_when_remember_digest_is_wrong#SessionsHelperTest (2.99s)
ArgumentError: ArgumentError: wrong number of arguments (given 0, expected 1)
app/models/user.rb:51:in `authenticated?'
app/helpers/sessions_helper.rb:31:in `current_user'
test/helpers/sessions_helper_test.rb:24:in `block in <class:SessionsHelperTest>'
ERROR["test_current_user_returns_right_user_when_session_is_nil", #<Minitest::Reporters::Suite:0x0000563b1d778130 @name="SessionsHelperTest">, 3.0089105970000674]
test_current_user_returns_right_user_when_session_is_nil#SessionsHelperTest (3.01s)
ArgumentError: ArgumentError: wrong number of arguments (given 0, expected 1)
app/models/user.rb:51:in `authenticated?'
app/helpers/sessions_helper.rb:31:in `current_user'
test/helpers/sessions_helper_test.rb:14:in `block in <class:SessionsHelperTest>'
ERROR["test_authenticated?_should_return_false_for_a_user_with_nil_digest", #<Minitest::Reporters::Suite:0x0000563b1c85c188 @name="UserTest">, 3.3450159330000133]
test_authenticated?_should_return_false_for_a_user_with_nil_digest#UserTest (3.35s)
ArgumentError: ArgumentError: wrong number of arguments (given 0, expected 1)
app/models/user.rb:51:in `authenticated?'
test/models/user_test.rb:88:in `block in <class:UserTest>'
44/44: [============================] 100% Time: 00:00:03, Time: 00:00:03
Finished in 3.66714s
44 tests, 171 assertions, 0 failures, 3 errors, 0 skips
####current_user内の抽象化したauthenticated?メソッド
app/helpers/sessions_helper.rb
module SessionsHelper
.
.
.
def current_user
if (user_id = session[:user_id])
# ログイン中のidを代入する
# 永続セッション(ログイン中)にするため
@current_user ||= User.find_by(id: user_id)
# カレントユーザーまたは検索結果があったものを代入する
elsif (user_id = cookies.signed[:user_id])
#
user = User.find_by(id: user_id)
# idでユーザーを探す
if user && user.authenticated?(:remember, cookies[:remember_token])
# ユーザーが有効であり、記憶トークンも認証されたら
log_in user
# ログインする
@current_user = user
# カレントユーザーに代入するかな
end
end
end
.
.
.
end
####Userテスト内の抽象化したauthenticated?メソッド
test/models/user_test.rb
require 'test_helper'
class UserTest < ActiveSupport::TestCase
def setup
@user = User.new(name: "Example User", email: "user@example.com",
password: "foobar", password_confirmation: "foobar")
end
.
.
.
test "authenticated? should return false for a user with nil digest" do
assert_not @user.authenticated?(:remember, '')
# 空かどうか?
end
end
#####テスト
ubuntu:~/environment/sample_app (account-activation) $ rails t
Running via Spring preloader in process 7935
Started with run options --seed 61348
44/44: [============================] 100% Time: 00:00:02, Time: 00:00:02
Finished in 2.30102s
44 tests, 175 assertions, 0 failures, 0 errors, 0 skips
###演習
1.
コンソール内で新しいユーザーを作成してみてください。新しいユーザーの記憶トークンと有効化トークンはどのような値になっているでしょうか? また、各トークンに対応するダイジェストの値はどうなっているでしょうか?
#<User id: 103, name: "d", email: "abc2@def.com", created_at: "2021-10-18 07:01:26", updated_at: "2021-10-18 07:01:26", password_digest: [FILTERED], remember_digest: nil, admin: false, activation_digest: "$2a$12$IIiEogZPhOFemhstZ6kEmuboRLSRHUToOlp/ffbUivv...", activated: false, activated_at: nil>
リスト 11.26で抽象化したauthenticated?メソッドを使って、先ほどの各トークン/ダイジェストの組み合わせで認証が成功することを確認してみましょう
わからなかった。