Rails チュートリアル　第11章　まとめ

メールアドレスを使った認証の方法
8,9章の認証は
----.authenticate?(=====)
とするなら
sessionを使った認証では、----の部分がDBにあるpassword_digestであり、===の部分はUserに入力してもらったpasswordだった
cookiesを使った認証では、---はDBにあるremember_digestであり、===はcookiesに保存されているremember_token

今回の場合、
---の部分は
まず、DBに認証用のdigestを保存するために、開発側で平文を用意し、ハッシュ値をかけてDBに保存する

ユーザーに送ったメールにはactivation_tokenとユーザーのemail情報を含めている
ユーザーがlinkをクリックすると、GETリクエストが送られたとき、userのemail情報がparamsに格納されているので、それを使って、User情報を取得する、そのUser情報からDBのactivation_digestを取得する

そして、===にはactivation_tokenがそのまま入る、ここはsessionやcookiesと変わらない
そのactivation_digestとactivation_tokenを認証する

signup (必要事項の記入) =
before_action (tokenの発行、ハッシュ化してactivation_digestとしてDBに保存) ->
create action (User情報を格納、Userに認証用メールを送信,emailとactivation_tokenが送信) =
User.mailer (account_activationメソッドが呼び出される、controllerに相当) ->
app/views/user_mailer/account_activation.text.erb(メール文面,viewに相当) ->
Userがリンクをクリック = GETリクエストが送られる ->
ルーティングが作動 ->
accout_activation controllerのedit action (実際の認証)

リクエストが送られた時のルーティングとコントローラを準備する必要がある

ルーティング
GET /accout_activations/:id/edit
:id = token 
コントローラー
find_byでparams[:emailを取得
authenticated?で認証
params[:id]でtokenを受け取る

attr_accessor　activation_token
一時的に保存し、忘れる前にメールで送る
メソッド参照
メソッドをあとで作る
before_create　signupの時にだけ反応するコールバック処理
before_save 保存する前に必ず行うコールバック処理
このコールバック処理をメソッド参照で行うことができる

```ruby:user.rb
#before_createなので、signupする直前に実行
    def create_activation_digest
      self.activation_token  = User.new_token 
#ランダムなトークンを発行し、attr_accessorで作った仮想的な属性に入れておく
      self.activation_digest = User.digest(activation_token)
#そのtokenをハッシュ化させたものをDBのdigestに保存
#正確にはdigestに代入されたあと、createで反映されるのでDBが保存される
    end
```

メールを送信 - GETリクエストを送る

```ruby:app/mailers/application_mailer.rb
  def account_activation
    @greeting = "Hi"
    mail to: "to@example.org"  # => return mail object
#中身はapp/views/user_mailer/account_activation.text.erbと
#app/views/user_mailer/account_activation.html.erbとなり
#account_ativationを呼び出した何かがmail objectを実行する ???
  end
```
textとhtml.erbはviewに似ている
Mailer =　Controller
Mailの文面 = View

メールのURLには平文とともにメールアドレスも入れないと誰の平文か分からない

リンクを作る（メールの文面）時に
`edit_user_url(user)`
が
`http://www.example.com/users/1/edit`になるらしいが、どこでやった？

その考え方を使い、
`http://www.example.com/account_activations/q5lt38hQDc_959PVoo6b7A/edit`
としたい

さらに、メールアドレスも含めた文面にしたいので、
`account_activations/q5lt38hQDc_959PVoo6b7A/edit?email=foo%40example.com`
としたいので、
引数にemailも加えて、最終的に

```
edit_account_activation_url(@user.activation_token, email: @user.email)
```

となる


コントローラーの実装
create controller - 
User登録をする時に、アカウント認証を行いたいのでcreateアクションに組み込む
今まではsignup(userに必要事項を記入）したら、すぐにloginできる仕様だった

```ruby:user_controller
def create
    @user = User.new(user_params)
    if @user.save
      log_in @user
      flash[:success] = "Welcome to the Sample App!"
      redirect_to user_path(@user)
    else
      render "new"
    end
end
```
今回は、これを修正して


```ruby:user_controller
def create
    @user = User.new(user_params)
    if @user.save
      UserMailer.account_activation(@user).deliver_now
#user_mailer.rbのaccout_activationが呼び出されている
#deliver_nowは返ってきたメールオブジェクトを実際に送信する
      flash[:info] = "Please check your email to activate your account."
      redirect_to root_url
    else
```

-----

edit actionの実装

```ruby:accout_activation_controller
@user = User.find_by(email: params[:email]) #　メールからUser情報を取得
@user.authenticated?(params[:id]) 
#DBのactivation_digestとメールから取得したactivation_tokenを照合する
```

しかし、authenticatedメソッドはrememberで固定してしまっているので、DRYの観点からauthenticated?メソッドを抽象化しなければ使えない

元々のauthenticated?メソッドは

```ruby:
def authenticated?(remember_token)
    BCrypt::Password.new(remember_digest).is_password?(remember_token)
end
```
だったが、以下のように変更

```ruby:
def authenticated?(attribute, token)
  digest = self.send("#{attribute}_digest") 
#sendを使えば動的にメソッドを変更できる、式展開をより実際的に使うことができる
  return false if digest.nil?
  BCrypt::Password.new(digest).is_password?(token)
end
```

これでeditアクションでauthenticated?を使うことができる

```ruby:account_activation.cotroller
def edit
    user = User.find_by(email: params[:email])
    if user && !user.activated? && user.authenticated?(:activation, params[:id])
#userはnilチェック
#!user.activated?は何回もクリックされないために、activatedがfalseである時だけ実行
#authenticated?は認証
      user.update_attribute(:activated,    true) #activatedを有効に
      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
```