初心者がDeviseのコードを見て仕組みをふんわり理解する【registrations new】

##はじめに
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
```ruby:devise/app/controllers/devise/registrations_controller.rb
# frozen_string_literal: true
```
コメントアウトされていますが、Rubyのバージョンアップに備えた1文のようです。
参考：frozen_string_literalが入って気づいた、メソッド設計の原則
https://qiita.com/jkr_2255/items/300b5db8c1f04e1e2815

####prepend_before_action
```ruby:devise/app/controllers/devise/registrations_controller.rb
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

####newアクション

```ruby:devise/app/controllers/devise/registrations_controller.rb 
 # GET /resource/sign_up
  def new
    build_resource
    yield resource if block_given?
    respond_with resource
  end
```
- ログインするための最初の部分です。
- `resource`はすでにdevise_controllerで定義されています。

```ruby:devise/app/controllers/devise_controller.rb
  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
- `resource`内に`#{resource_name}`という変数がありますが、その下部分で定義されています。
- `devise_mapping.name`こちらもすでにdevise_controllerで定義されてますが、引用記事をみると、別部分にヒントがありそうです。


```ruby:devise/app/controllers/devise_controller.rb
  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`があります。セッションを新しく作るという意味のようです。

```ruby:devise/app/controllers/devise/registrations_controller.rb 
 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


- `respond_with resource`はこの辺が関係して呼び出されるのかなと思いますが、


```ruby:devise/app/controllers/devise_controller.rb
  def respond_with_navigational(*args, &block)
    respond_with(*args) do |format|
      format.any(*navigational_formats, &block)
    end
  end
```

```ruby:devise/app/controllers/devise_controller.rb
  def navigational_formats
    @navigational_formats ||= Devise.navigational_formats.select { |format|Mime::EXTENSION_LOOKUP[format.to_s] }
  end
```

ピンときていないので勉強して加筆・修正したいと思います。

ここまで複雑なんだなGem...たった数行のコードにいろいろなものが凝縮されていてとても勉強になりました。
newアクションしか書けなかったけど、最終的にはregistrations_controllerの各アクションのだけでも読んだ記録を残したい...！



お付き合いいただきありがとうございました。