##はじめに
Railsチュートリアルがやっと1周終わった者です。
gemのDeviseを入れてみたら、Railsチュートリアルでの5~6章分の実装が10分ほどで終わり、驚愕しています。
なんでできたのか分からない...
そして、テストも書きたいけどどうやって書いたらいいのか分からない。
そこで「公式やソースコードを読みましょう」ということで、読んでみたけどこれまたさっぱり分からない...ながらも少しずつ理解したいので、読み込んでいきます。
読みながら自分が調べたことをまとめていきます。
・初心者だけどDeviseの仕組みを知りたい
そんな方の役に立てたら幸いです。
DeviseのGitHub
https://github.com/heartcombo/devise
##Deviseの概要
GitHubのREADMEの一番最初に、概要の説明があります。
- Deviseは、Wardenに基づくRails向けの柔軟な認証ソリューションです。
- ラックベースです。
- Railsエンジンに基づく完全なMVCソリューションです。
- 複数のモデルに同時にサインインさせることができます。
- モジュール性の概念に基づいています。本当に必要なものだけを使用してください。
ここで出てくるWardenというのは認証のためのgemで、devise内でこれを引っ張ってきているようです。
また、10個のモジュールで構成されており、必要なものはコメントアウトを外したりしながら使ってね、ということのようです。
モジュールというかもう機能ですね。
Qiita内で表にしてくださっている方がいらっしゃったので、下記にて引用します。
|機能 | 概要|
|:-----------------|:----------------|
|database_authenticatable |サインイン時にユーザーの正当性を検証するためにパスワードをハッシュ化してDBに登録します。認証方法としてはPOSTリクエストかHTTP Basic認証が使えます。|
|registerable |登録処理を通してユーザーをサインアップします。また、ユーザーに自身のアカウントを編集したり削除できるようにします。|
|recoverable |パスワードをリセットし、それを通知します。|
|rememberable |保存されたcookieから、ユーザーを記憶するためのトークンを生成・削除します。|
|trackable |サインイン回数や、サインイン時間、IPアドレスを記録します。|
|validatable |Emailやパスワードのバリデーションを提供します。独自に定義したバリデーションを追加することもできます。|
|confirmable |メールに記載されているURLをクリックして本登録を完了する、といったよくある登録方式を提供します。また、サインイン中にアカウントが認証済みかどうかを検証します。|
|lockable |一定回数サインインを失敗するとアカウントをロックします。ロック解除にはメールによる解除か、一定時間経つと解除するといった方法があります。|
|timeoutable |一定時間活動していないアカウントのセッションを破棄します。|
|omniauthable |intridea/omniauthをサポートします。TwitterやFacebookなどの認証を追加したい場合はこれを使用します。|
引用元:[Rails] deviseの使い方(rails6版)
https://qiita.com/cigalecigales/items/16ce0a9a7e79b9c3974e
また、Deviseは、コントローラーとビュー内で使用するヘルパーを作成します。
よく使用するコマンドを予め設定したものです。
デバイスモデルが 'User'であると想定してヘルパー名は例示していますが、
デバイスモデルがユーザー以外の場合は、「_ user」を「_yourmodel(任意のモデル名)」に置き換えると、同じロジックが適用されます。
こちらもQiita内で表にしてくださっている方がいらっしゃったので引用します。
|メソッド | 用途 |
|:-----------------|:----------------|
|before_action :authenticate_user!|コントローラーに設定して、ログイン済ユーザーのみにアクセスを許可する|
|user_signed_in?|ユーザーがサインイン済かどうかを判定する|
|current_user|サインインしているユーザーを取得する|
|user_session|ユーザーのセッション情報にアクセスする|
引用元:Rails deviseで使えるようになるヘルパーメソッド一覧
https://qiita.com/tobita0000/items/866de191635e6d74e392
##registrations -- サインアップ・アカウント編集・削除
最も基本的なアカウントのCRUDはこのregistrationが担っているので、ここが分からないと応用の機能部分のコードリーディングは難しそうです。
コード全体を上から調べた範囲で記入してきますが、ちまちま区切っていくので、分かりくいかもしれません。
横にソースコードを置きながら見てもらえれば、ちょっとは分かりやすいかも...
####frozen_string_literal
# frozen_string_literal: true
コメントアウトされていますが、Rubyのバージョンアップに備えた1文のようです。
参考:frozen_string_literalが入って気づいた、メソッド設計の原則
https://qiita.com/jkr_2255/items/300b5db8c1f04e1e2815
####prepend_before_action
class Devise::RegistrationsController < DeviseController
prepend_before_action :require_no_authentication, only: [:new, :create, :cancel]
prepend_before_action :authenticate_scope!, only: [:edit, :update, :destroy]
prepend_before_action :set_minimum_password_length, only: [:new, :edit]
-
DeviseController
を継承しています。
ソースコードのファイルをみると、devise_controller.rbはこのモジュールだけでなく、すべてのモジュールへ引き継いでいます。 -
prepend_before_action
はbefore_action
より前に実行するメソッドです。アクセスできるアクションがユーザーのログイン状態で制限されるようにしています。- 参考:Railsドキュメント
https://railsdoc.com/page/prepend_before_action
- 参考:Railsドキュメント
####newアクション
# GET /resource/sign_up
def new
build_resource
yield resource if block_given?
respond_with resource
end
- ログインするための最初の部分です。
-
resource
はすでにdevise_controllerで定義されています。
def resource
instance_variable_get(:"@#{resource_name}")
end
# Proxy to devise map name
def resource_name
devise_mapping.name
end
alias :scope_name :resource_name
-
instance_variable_get
メソッドはインスタンス変数の値を取得して返します。@user =
からの定義と同じもののようです。- 参考:Ruby 2.7.0 リファレンスマニュアル
https://docs.ruby-lang.org/ja/latest/method/Object/i/instance_variable_get.html
- 参考:Ruby 2.7.0 リファレンスマニュアル
-
resource
内に#{resource_name}
という変数がありますが、その下部分で定義されています。 -
devise_mapping.name
こちらもすでにdevise_controllerで定義されてますが、引用記事をみると、別部分にヒントがありそうです。
def devise_mapping
@devise_mapping ||= request.env["devise.mapping"]
end
nameに注目してコードを繋げてみると、認証モデルがUserである場合は@singular = :users.to_s.tr('/', '_').singularize.to_symとみれます。singularizeは複数形を単数形に変換するメソッドで、最終的に@singular = :userとなりsingularのエイリアスがnameとなっているのでmapping.nameで:userが取得できます。
するとdefine_methodsの引数に:userが渡されauthenticate_user!が出来上がるという流れになっています。
引用元:DeviseのコードリーティングでRailsを学ぶ
https://qiita.com/irisAsh/items/513b8b58f54421b9a1a0
端的に言うと、mapping.name
で:user
が取得できるのでそれをresource_name
にしているということでしょうか。
-
registrations_controller
下部にbuild_resource
があります。セッションを新しく作るという意味のようです。
def build_resource(hash = {}) #build_resource(hash = {})の定義
self.resource = resource_class.new_with_session(hash, session)
end
- 元の
registrations_controller
上部へ戻りましょう。build
はほぼnew
と近い役割をしています。build_resource
という1文は、データベースから取り出したユーザーのインスタンス変数とセッションを作るということになります。 -
block_given?
はメソッドを実行する時にブロックが渡されていればtrueを返し、渡されていない時はfalseを返します。- ブロックとは
do ... end または { ... } で囲まれたコードの断片 (ブロックと呼ばれる)を後ろに付けてメソッドを呼び出すと、そのメソッドの内部からブロックを評価できます。ブロック付きメソッドを自分で定義するには yield 式を使います。
引用元:Ruby 2.7.0 リファレンスマニュアル
https://docs.ruby-lang.org/ja/latest/doc/spec=2fcall.html#block
-
この場合、resourceブロックを
yield
として定義し、block_given?
で真偽を確認している。yieldについて詳しく知る必要がありそうです。- 参考:yield @variable if block_given? ってなに?
https://kossy-web-engineer.hatenablog.com/entry/2020/01/19/094958- - 参考:【Ruby入門】yieldの使い方まとめ
https://www.sejuku.net/blog/20478
- 参考:yield @variable if block_given? ってなに?
-
respond_with resource
はこの辺が関係して呼び出されるのかなと思いますが、
def respond_with_navigational(*args, &block)
respond_with(*args) do |format|
format.any(*navigational_formats, &block)
end
end
def navigational_formats
@navigational_formats ||= Devise.navigational_formats.select { |format|Mime::EXTENSION_LOOKUP[format.to_s] }
end
ピンときていないので勉強して加筆・修正したいと思います。
ここまで複雑なんだなGem...たった数行のコードにいろいろなものが凝縮されていてとても勉強になりました。
newアクションしか書けなかったけど、最終的にはregistrations_controllerの各アクションのだけでも読んだ記録を残したい...!
お付き合いいただきありがとうございました。