アラフォーがお金持ちになるためプログラマ目指すブログ

お金も根性も学歴もないアラフォーまきのがエンジニア…じゃない、プログラマになってお金持ち目指すよ!

【Railsメモ】deviseでメアド変更した時の再認証をスキップする

どうも、アラフォーまきのです。



今作っているアプリ、deviseが入ってるんですが、confirmableをONにしてるんです。



こうすると、メアドを変更した時も新しいメアドに「確認してくれー」と認証メールが飛ぶんです。



これいらないって思って、色々確認してなんとかなったのでメモしておきます。
マジでおっかなびっくりやって「これでいいのかよ!」ってなったのでw




こまけぇこたぁ後にして、とりあえずどういうことをしたのか書きます。

0:必要な事

とりあえずviewがあればdevise自体は最低限仕事してくれるんですが、controllerも必要なので、まだ生成してない場合は作る。


$ rails g devise:controllers model名

model名は認証に使うモデルの名前。
よくあるのはuserですかね。
memberとかcustomerとか名付けられているのもたまに聞きますかね。



私はuserと名付けてました。



で、作るのはコントローラなので基本複数形。
だから

$ rails g devise:controllers users

するとこんな感じで元々users_controllerだったところが、usersというフォルダになって、その中にdeviseが持ってるcontroller群が出来上がる。


f:id:MmRevorution:20190621133546p:plain



1:registrations_controller.rbをいじる


メアドの変更があったとき、認証メールは送らずに変更をすぐに完了してあげたいので、いじるのはupdateの部分。




【旧】

# PUT /resource
# def update
  # super
# end


【新】

def update
  self.resource = resource_class.to_adapter.get!(send(:"current_#{resource_name}").to_key)
  prev_unconfirmed_email = resource.unconfirmed_email if resource.respond_to?(:unconfirmed_email)

  resource.skip_reconfirmation! # メアド更新時に認証メールを飛ばさないためのメソッド
  resource_updated = update_resource(resource, account_update_params)
  yield resource if block_given?
  if resource_updated
    set_flash_message_for_update(resource, prev_unconfirmed_email)
    bypass_sign_in resource, scope: resource_name if sign_in_after_change_password?

    respond_with resource, location: after_update_path_for(resource)
  else
    clean_up_passwords resource
    set_minimum_password_length
    respond_with resource
  end
end


こんだけ!



なんかめっちゃ難しいこと書いてある。
でもコードの意味はわかんない。
自分で考えたんじゃないんです。





GitHubのdeviseが持ってる本来のコードをコピペして、一言足しただけ。


2:細かい覚書き


ここからは一体何を悩んだのか、初心者目線での悩みの流れをw




まず、以下の参考サイトをみつけたんですよ。


参考1
qiita.com


参考2
stackoverflow.com


参考3(GitHub)
github.com




で、どうやらskip_reconfirmation!を使えばいいらしいことはわかったんです。




でも、実際どこに書けばいいのかがわからなかった。




今deviseでみられるcontrollerは全て中身がsuperとなっていて、書けそうな場所ないじゃないか…と。




参考3を見るとdevise/lib/devise/models/confirmable.rbとあるので、このファイルのdef skip_reconfirmation!のコメントはずせばいいのか…?とか考えてた。



だけどdevise/lib/devise/models/confirmable.rbなんてファイルはもちろん手元に見える形では落ちてきてない。




いくらエディタで検索かけても出てこない。




ここで初めて、私はsuperを認識したんです。
さっき確かに一度はdeviseのcontrollerを全て見てsuperとなっているのを確認してます。



だけどこの時superはただのsuperという文字でしか見てなかった。




思い出せ、superとはなんだったのか。
そう、継承した親クラスのメソッドを引っ張る時この一言でよかったじゃないか。




だから、これは親のメソッドの中身が隠れてるんだ!




親は誰だ!親の顔が見たい!いやまずどのcontrollerが対象なんだ!




unlocks_controllerpassword_controlleromniauth_callbacks_controller、お前らは違うな!



残りはconfirmations_controllerregistrations_controllersessions_controllerだな!




confirmations_controllerは認証か!怪しいな!




registrations_controllerは登録か!お前も怪しいな!




sessions_controllerはログイン制御系だな!お前は違うな!




お前らの中にupdateを持ってるのはregistrations_controllerお前一人だ!おまえだな!




と、対象のcontrollerを絞り込むというやり方w




registrations_controllerの一番上を見るとこう書いてあるのです。


class Users::RegistrationsController < Devise::RegistrationsController

で、GitHubで同じregistrations_controllerを確認すると以下のようになってました。

GitHub
github.com

class Devise::RegistrationsController < DeviseController



ここでやっと私は

「手元のregistrations_controllerDevise::RegistrationsControllerを継承していて、そのDevise::RegistrationsControllerはGitHubにいるやつだ!」

とわかりました。





諸先輩方はもちろんここまで読み込まなくたってすぐわかると思いますがw




だから

def update
  super
end

は実際はここに書かれている通り

def update
  self.resource = resource_class.to_adapter.get!(send(:"current_#{resource_name}").to_key)
  prev_unconfirmed_email = resource.unconfirmed_email if resource.respond_to?(:unconfirmed_email)

  resource_updated = update_resource(resource, account_update_params)
  yield resource if block_given?
  if resource_updated
     set_flash_message_for_update(resource, prev_unconfirmed_email)
    bypass_sign_in resource, scope: resource_name if sign_in_after_change_password?

  respond_with resource, location: after_update_path_for(resource)
  else
    clean_up_passwords resource
    set_minimum_password_length
    respond_with resource
  end
end


だったということを理解。




なるほどな。





ってことは、おそらくregistrations_controllerのupdateにskip_reconfirmation!を入れるんだろうと思うけれど、さてどこにかけばいいのだろう?





ここで参考1をもう一度確認すると、saveの前にかけばおkとある。





registrations_controllerをよーく読むと、updateでデータの保存をしているのは、おそらく上から3行目のここ

resource_updated = update_resource(resource, account_update_params)

なので、この直前にskip_reconfirmation!を入れてみることに。




ただ、参考1にある通り

user.skip_reconfirmation!


みたいにならないといけない。
ここでいうuserはどうやらresourceらしいと目星をつけた。




目星がついたのは、参考1のcreateの場合の記述が

user = User.new(params)
user.skip_confirmation!
user.save #ココ


ってなってて、GitHubのregistrations_controllerにあるcreateをみたら

def create
    build_resource(sign_up_params)

    resource.save #ココ
 :
end


ってなってたから。



なるほど、コードを書く人やニーズによって、userになるのかmemberになるのかもっと違うモデル名になるのか、そんなんわからないもんね。




resource.saveと書くことで、対象のモデル.saveという意味になるんだ!と。





なわけで、先ほど書いたような【新】の書き方にいたり、ローカルと本番と試したところ、認証メールを出さずにメアドの更新ができたことを確認できた、ということでした。


3:なんで認証メールスキップしたかったのか


実際にテストしてみたら、サインアップの認証時もメアドを変更した時も、views/devise/mailer/confirmation_instructions/html.erbの中身がメールされるんですね。



しらんかった。
あくまでも初回のサインアップ認証のものだと誤解してた。




実は、サインアップして認証メールのリンクを踏んだら、how_toのページにジャンプする設定をしてあるんですよ。




だから、メアド変更で認証リンク踏んでも、同様にhow_toにとんじゃう事が発覚。




こりゃいかんと思って、最初は認証前かどうかで分岐すればいいんじゃないか?って考えたんです。




実際に触ったり調べてわかったのが、deviseが認証したと判断するのが、confirmed_atにutcが入った時点なんですよね。




それって、ユーザーが認証メールのリンクを踏んだその瞬間に、その日時がutcとして入れられちゃうので、データーが飛んだ時点ではもう認証済みになっちゃう。



だからifで分岐することはできないんだなーって。




そうしたら、あとすぐにできそうなのはメアド変更の時は認証メールを出さないという動作にすることくらいしか思いつかなかった、というわけでした。