11.3 アカウントを有効化する
AccountActivationsコントローラのeditアクションを書いていく。アクションへのテストを書き、しっかりとテストできていることが確認できたら、AccountActivationsコントローラからUserモデルにコードを移していく作業(リファクタリング)にも取り掛かかる。
11.3.1 authenticated?メソッドの抽象化
sendメソッドを使うと共通化できる。
sendメソッド とは?
渡されたオブジェクトに「メッセージを送る」ことによって、呼び出すメソッドを動的に決めることができるメソッド。
>> user = User.first
>> user.activation_digest
=> "$2a$10$4e6TFzEJAVNyjLv8Q5u22ensMt28qEkx0roaZvtRcp6UZKRM6N9Ae"
>> user.send(:activation_digest)
=> "$2a$10$4e6TFzEJAVNyjLv8Q5u22ensMt28qEkx0roaZvtRcp6UZKRM6N9Ae"
>> user.send("activation_digest")
=> "$2a$10$4e6TFzEJAVNyjLv8Q5u22ensMt28qEkx0roaZvtRcp6UZKRM6N9Ae"
# 文字列も渡すことができる
>> attribute = :activation
# シンボルを代入
>> user.send("#{attribute}_digest")
=> "$2a$10$4e6TFzEJAVNyjLv8Q5u22ensMt28qEkx0roaZvtRcp6UZKRM6N9Ae"
# 式展開を使って渡す
演習 1
コンソール内で新しいユーザーを作成してみてください。新しいユーザーの記憶トークンと有効化トークンはどのような値になっているでしょうか? また、各トークンに対応するダイジェストの値はどうなっているでしょうか?
>> user = User.create(name: "sample", email: "sample@email.com", password: "sample", password_confirmation: "sample")
(4.2ms) SELECT sqlite_version(*)
(0.1ms) begin transaction
User Exists? (1.8ms) SELECT 1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) LIMIT ? [["email", "sample@email.com"], ["LIMIT", 1]]
User Create (8.4ms) INSERT INTO "users" ("name", "email", "created_at", "updated_at", "password_digest", "activation_digest") VALUES (?, ?, ?, ?, ?, ?) [["name", "sample"], ["email", "sample@email.com"], ["created_at", "2021-03-02 04:11:18.445221"], ["updated_at", "2021-03-02 04:11:18.445221"], ["password_digest", "$2a$12$.NT19iae7r0WhFR2OcikiemF5WD3QvT0aA4/LJUSZ9UHYOt4XB0T."], ["activation_digest", "$2a$12$jKN4cC3pqjMUIKs4/EvhIOOza0GueGUAKzsGgaMLixzoGHv/Moo5W"]]
(5.5ms) commit transaction
=> #<User id: 102, name: "sample", email: "sample@email.com", created_at: "2021-03-02 04:11:18", updated_at: "2021-03-02 04:11:18", password_digest: [FILTERED], remember_digest: nil, admin: nil, activation_digest: "$2a$12$jKN4cC3pqjMUIKs4/EvhIOOza0GueGUAKzsGgaMLixz...", activated: false, activated_at: nil>
演習 2
リスト 11.26で抽象化したauthenticated?メソッドを使って、先ほどの各トークン/ダイジェストの組み合わせで認証が成功することを確認してみましょう。
>> user.remember_token
=> nil
>> user.remember_digest
=> nil
>> user.activation_token
=> "DQKQr9KfehJzT6PzsjrEdQ"
>> user.activation_digest
=> "$2a$12$jKN4cC3pqjMUIKs4/EvhIOOza0GueGUAKzsGgaMLixzoGHv/Moo5W"
11.3.2 editアクションで有効化
editアクションを書いていく。
# GET /account_acrivations/:id/edit
user = User.find_by(email: params[:email])
# emailを元にuserを探して変数userに代入
if user && !user.activated? && user.authenticated? (:activation, params[:id])
# 左: nilかどうかを確認
# 中: userがactivatedされていないかを確認
# 右: :activation, params[:id]の2つで認証する
user.update_attribute(:activated, true)
user.update_attribute(:activated_at, Time.zone.now)
log_in user
# ログイン
flash[:success] = "Account activated!"
redirect_to user
# プロフィールページにアクセス
else
flash[:danger] = "Invalid activation link"
redirect_to root_url
end
end
演習 1
コンソールから、11.2.4で生成したメールに含まれているURLを調べてみてください。URL内のどこに有効化トークンが含まれているでしょうか?
----==_mimepart_603dc222a8f1c_14022ad7ea9149d8402a2
Content-Type: text/plain;
charset=UTF-8
Content-Transfer-Encoding: 7bit
Hi test,
Welcome to the Sample App! Click on the link below to activate your account:
http://localhost:3000/account_activations/exKxGElXRHOQ6VyC0ia4MA/edit?email=test%40example.com
この部分がトークン: account_activations/exKxGElXRHOQ6VyC0ia4MA/
演習 2
先ほど見つけたURLをブラウザに貼り付けて、そのユーザーの認証に成功し、有効化できることを確認してみましょう。また、有効化ステータスがtrueになっていることをコンソールから確認してみてください。
>> user = User.find_by(name: "test")
User Load (3.3ms) SELECT "users".* FROM "users" WHERE "users"."name" = ? LIMIT ? [["name", "test"], ["LIMIT", 1]]
=> #<User id: 103, name: "test", email: "test@example.com", created_at: "2021-03-02 04:42:10", updated_at: "2021-03-02 04:45:05", password_digest: [FILTERED], remember_digest: nil, admin: nil, activation_digest: "$2a$12$MmJY.TpGghnO.LdaF6GSPO8xQUWDIPzzl0L3ISc5FYU...", activated: true, activated_at: "2021-03-02 04:45:05">
>> user.activated
=> true
11.3.3 有効化のテストとリファクタリング
アカウント有効化の統合テストを追加する。
test "valid signup information with account activation" do
get signup_path
# signup_pathにアクセスする
assert_difference 'User.count', 1 do
# ユーザー数が1つ増えているか確認。増えたユーザーの情報は以下。
post users_path, params: { user: { name: "Example User", email: "user@example.com", password: "password", password_confirmation: "password" } }
end
assert_equal 1, ActionMailer::Base.deliveries.size
# メールを1つ送っているか確認
user = assigns(:user)
# バリデーションされていない@userにアクセスする
assert_not user.activated?
# userが有効化されていないか確認
log_in_as(user)
# 有効化していない状態でログインしてみる
assert_not is_logged_in?
# ログインできなかったらtrue
get edit_account_activation_path("invalid token", email: user.email)
# edit_account_activation_pathにトークンとメールアドレスを渡してアクセス
assert_not is_logged_in?
# ログインできなかったらtrue
get edit_account_activation_path(user.activation_token, email: 'wrong')
# トークンは正しいが、メールアドレスが無効の場合でアクセス
assert_not is_logged_in?
# ログインできなかったらtrue
get edit_account_activation_path(user.activation_token, email: user.email)
# トークンもメールアドレスも有効の場合でアクセス
assert user.reload.activated?
# ユーザーが更新されたらtrue
follow_redirect!
# リダイレクトされる
assert_template 'users/show'
# users/showを描画しているか確認
assert is_logged_in?
# ログインできていたらtrue
end
演習 1
リスト 11.35にあるactivateメソッドはupdate_attributeを2回呼び出していますが、これは各行で1回ずつデータベースへ問い合わせしていることになります。リスト 11.39に記したテンプレートを使って、update_attributeの呼び出しを1回のupdate_columns呼び出しにまとめてみましょう。これでデータベースへの問い合わせが1回で済むようになります(注意!update_columnsは、モデルのコールバックやバリデーションが実行されない点がupdate_attributeと異なります)。また、変更後にテストを実行し、 green になることも確認してください。
# アカウントを有効にする
def activate
update_columns(activated: true, activated_at: Time.zone.now)
end
演習 2
現在は、/usersのユーザーindexページを開くとすべてのユーザーが表示され、/users/:idのようにIDを指定すると個別のユーザーを表示できます。しかし考えてみれば、有効でないユーザーは表示する意味がありません。そこで、リスト 11.40のテンプレートを使って、この動作を変更してみましょう9 。なお、ここで使っているActive Recordのwhereメソッドについては、13.3.3でもう少し詳しく説明します。
def index
@users = User.where(activated: true).paginate(page: params[:page])
end
def show
@user = User.find(params[:id])
redirect_to root_url and return unless @user.activated?
end
11.4 本番環境でのメール送信
サンプルアプリケーションの設定を変更し、production環境で実際にメールを送信できるようにする。
演習 1
演習 2
$ heroku push
時にPrecompiling assets failed.
が出て、解決できなかったので後に回します。
エラーメモ
たぶんここで引っかかってるけど、調べてもよくわからない。
$ git push heroku
.
.
.
remote: SyntaxError: /tmp/build_bb915e0f/config/environments/production.rb:84: syntax error, unexpected '\n', expecting =>
.
.
.
remote: !
remote: ! Precompiling assets failed.
remote: !
remote: ! Push rejected, failed to compile Ruby app.
remote:
remote: ! Push failed
.
.
.
さいごに
メール認証難しかったです!