railsのgem deviseのログインページを開くコントローラを読解してみた


rails読解シリーズ、第4回はdeviseのコントローラを読解してみました。
いつも深入りしすぎて、全体像が見えづらくなっているので今回はあっさりめに読んでみました。

ログインページを開くための、session#newアクションを見ていきます

session#new
def new
  self.resource = resource_class.new(sign_in_params)
  clean_up_passwords(resource)
  yield resource if block_given?
  respond_with(resource, serialize_options(resource))
end
2行目
self.resource = resource_class.new(sign_in_params)

resource_classによってdeviseを適用しているモデル、例えばUserモデルが呼ばれます。
sign_in_paramsによりストロングパラメータを通した値が、Userモデルに格納されます。

resource_classメソッドの中の動作を見たものの、いまいちなぜ、Userモデルが呼び出されるのかは理解しきれませんでした😅
resource_classメソッドを定義している理由はおそらく、deviseの適用モデルを柔軟に変えることができるようにするためかと推測しています。

3行目
clean_up_passwords(resource)

clean_up_passwordsメソッドにより、resourceに格納されたUserモデルのpassword、password_confirmation属性を空にする(nilを代入する)

4行目
yield resource if block_given?

コントローラにブロックが渡された場合はyield resourceが実行される。
これはdeviseのデフォルトコントローラを再利用しつつ、追加で機能を追加する場合に機能する。
deviseコントローラをカスタマイズするため、のgenerateコマンドをターミナルで実行すると生成されるコントローラは、

カスタマイズ用に生成されるコントローラ
def new
  super
end

のように生成される。
このときsuperにブロックを引数と渡すことで、ブロック内の動作をdeviseのコントローラ動作に追加することができる。

deviseのデフォルトの動作を残しつつ、カスタマイズする方法
def new
  super { |resource| ... }
end

逆にdeviseのデフォルトコントローラ側ではsuperメソッドによってブロックが渡されていないか確認し、渡されている場合はyield resourceによってブロックのコードを実行する。

5行目
  respond_with(resource, serialize_options(resource))

respond_withメソッドは、こちらによると、httpレスポンスを生成するようです。

クライアント側が指定したmimetypeに応じて、レスポンスは生成できます。
mimetypeとはデータの形式のことで、HTML形式やjson形式などが含まれます。
中のコードは見ていませんが、第2引数のserialize_options(resource)でmimetypeの指定ができるようになっているのかと思われます。

ソースコード
# File actionpack/lib/action_controller/metal/mime_responds.rb, line 323
    def respond_with(*resources, &block)
      raise "In order to use respond_with, first you need to declare the formats your "              "controller responds to in the class level" if self.class.mimes_for_respond_to.empty?

      if collector = retrieve_collector_from_mimes(&block)
        options = resources.size == 1 ? {} : resources.extract_options!
        options[:default_response] = collector.response
        (options.delete(:responder) || self.class.responder).call(self, resources, options)
      end
    end

中身の動きに関してはrespond_toに関する記事が参考になりそう。

まとめ

収穫はdeviseのデフォルトコントローラに上書き、ではなく追加する方法がわかったことですかね。
かなりシンプルな動きです。
どちらかというとcurrent_userとかヘルパーメソッドの定義のほうが気になります。

短いですが今回は以上です。

おまけ

resource_classメソッドについて、わかるところまで。
ほぼメモ書きです。

class DeviseController < Devise.parent_controller.constantize

def resource_class
  devise_mapping.to
end

def devise_mapping
  @devise_mapping ||= request.env["devise.mapping"]
end

@devise_mappingにはMappingモデルのインスタンスが代入されている。
Mappingモデルにはtoメソッドが定義されている。
toメソッドはDeviseMappingモデルのklass属性の値を呼び出す。
klass属性の中にはUserモデルが格納されている。

Mappingモデル
class Mapping #:nodoc:
  attr_reader :singular, :scoped_path, :path, :controllers, :path_names,
              :class_name, :sign_out_via, :format, :used_routes, :used_helpers,
              :failure_app, :router_name

def to
  @klass.get
end

このとき@devise_mappingにどのようにして、Mappingモデルの値が渡されているのか、さらにUserモデルの情報が渡されているのか追いきれませんでした。