Rails初心者がdeviseのsessions#createを読み解く（途中）

ログインをmodalで実装してるんだけど、validationにひっかかったときに
registrations#newに遷移してしまう。
→modal上で完結させたい。

devise触りたてでソース読んでないので、流れとか整理しておく。

```ruby:devise/registrations_controller.rb
  def create
    build_resource(sign_up_params) #01
    resource.save
    yield resource if block_given?
    if resource.persisted?
      if resource.active_for_authentication? #02
        set_flash_message! :notice, :signed_up #03
        sign_up(resource_name, resource) #04
        respond_with resource, location: after_sign_up_path_for(resource) #05
      else
        set_flash_message! :notice, : "signed_up_but_#{resource.inactive_message}"
        expire_data_after_sign_in! #[06]
        respond_with resource, location: after_inactive_sign_up_path_for(resource) #07
      end
    else
      clean_up_passwords resource #08
      set_minimum_password_length #09
      respond_with resource
    end
  end
```


#01  build_resource(sign_up_params)

```ruby:devise/registrations_controller.rb
  # Build a devise resource passing in the session. Useful to move
  # temporary session data to the newly created user.
  def build_resource(hash = {})
    self.resource = resource_class.new_with_session(hash, session)
  end

```



●recource_classに関して

```ruby:devise/devise_controller.rb
 # Proxy to devise map class
  def resource_class
    devise_mapping.to
  end
```

```ruby:devise/devise_controller.rb
  # Attempt to find the mapped route for devise based on request path
  def devise_mapping
    @devise_mapping ||= request.env["devise.mapping"]
  end
```

```ruby:lib/devise/mapping.rb
  # Gives the class the mapping points to.
  def to
    @klass.get
  end
```
resource_class = Devise.mappingsの@klassってことね。

request.env：
https://qiita.com/kentosasa/items/a0500f2f21b33b29beb6




●new_with_sessionに関して

```ruby:devise/test/rails_app/lib/shared_user.rb
  module ExtendMethods
    def new_with_session(params, session)
      super.tap do |user|
        if data = session["devise.facebook_data"]
          user.email = data["email"]
          user.confirmed_at = Time.now
        end
      end
    end
  end
```


<b>.tap(Object)</b>
`obj.tap {|myself| block }`
tapメソッドは、ブロック変数にレシーバ自身を入れてブロックを実行します。
戻り値はレシーバ自身です。メソッドチェーンの中にtapメソッドをはさみ込み、
ソースコードを簡潔にする目的で使われます。
https://ref.xaio.jp/ruby/classes/object/tap


Facebook認証の場合、
resource_class(つまりは@klass)に対して.tapをするのかな？
Facebook認証じゃない場合は、そのまんま。


というわけで、
<b>build_resource(sign_up_params)
→受け取ったパラメータの中にsession["devise.facebook_data"]があれば、
→user.emailをそのパラメータの中に入っている:emailで上書きする
→confirmed_atは現在の（上書きした）時間</b>

facebook認証用のもの（？）


#02 active_for_authentication? 

●active_for_authentication?に関して

```ruby:devise/models/authenticatable.rb
def active_for_authentication?
  true
end
```

えっ。
と思ったらこんな注意書きが

```ruby:devise/models/authenticatable.rb
   # == active_for_authentication?
    #
    # After authenticating a user and in each request, Devise checks if your model is active by
    # calling model.active_for_authentication?. This method is overwritten by other devise modules. For instance,
    # :confirmable overwrites .active_for_authentication? to only return true if your model was confirmed.
    #
    # You can overwrite this method yourself, but if you do, don't forget to call super:
    #
    #   def active_for_authentication?
    #     super && special_condition_is_valid?
    #   end
    #
    # Whenever active_for_authentication? returns false, Devise asks the reason why your model is inactive using
    # the inactive_message method. You can overwrite it as well:
    #
    #   def inactive_message
    #     special_condition_is_valid? ? super : :special_condition_is_not_valid
    #   end
    #
```

```
ユーザを認証した後、deviseは`model.active_for_authentication?`を実行してモデルが本当にアクティブかを調べる。
このメソッドは他のdevice modulesによってオーバライトされる。
例えば、:confirmableモデルでは、モデルが確認できたらtrueを返すようにオーバライトしている。

オーバライトしてもいいけど、superを呼ぶのを忘れるなよ。


このメソッドがfalseを返すと、deviseはなんでモデルがアクティブじゃねーんだよ、と
inactive_messageメソッドを通じて聞いてくる。これもオーバライトしてよい
```

オーバライト前提だからtrueしか返してないんですかね。。とりあえず次


#03 set_flash_message! :notice, :signed_up

```ruby:devise/devise_controller.rb
  def set_flash_message(key, kind, options = {})
    message = find_message(kind, options) #refer below
    if options[:now]
      flash.now[key] = message if message.present?
    else
      flash[key] = message if message.present?
    end
  end
```
keyが:notice、:kindが:signed_upに対応してますね。
messageってのがあれば、messageをflashの中に入れるんですね。
それがoptions[:now]があるかどうかで入れ方が変わると。
んでそのmessageってのは、find_message(:signed_up)だと。

```ruby:devise/devise_controller.rb
  # Get message for given
  def find_message(kind, options = {})
    options[:scope] ||= translation_scope #refer below
    options[:default] = Array(options[:default]).unshift(kind.to_sym)
    options[:resource_name] = resource_name
    options = devise_i18n_options(options) #refer below
    I18n.t("#{options[:resource_name]}.#{kind}", options)
  end
```
`I18n.t`→I18n.translateの略。
翻訳したのをfind_messageの戻り値として返してくれるんですかね。
https://railsguides.jp/i18n.html
https://qiita.com/Kta-M/items/bd4ba36a58ad602a9d8b


```ruby:devise/devise_controller.rb
 　# Controllers inheriting DeviseController are advised to override this
  # method so that other controllers inheriting from them would use
  # existing translations.
  def translation_scope
    "devise.#{controller_name}"
  end
```

```ruby:
  def devise_i18n_options(options)
    options
  end
```


というわけで大まかにいうと、
<b>set_flash_message! :notice, :signed_up
↓
`flash[:notice] = :signed_up`</b>
flashを規定するもの（まあメソッド名からもわかるが

#04 sign_up(resource_name, resource)

```ruby:/app/controllers/devise/registrations_controller.rb
  # Signs in a user on sign up. You can overwrite this method in your own
  # RegistrationsController.
  def sign_up(resource_name, resource)
    sign_in(resource_name, resource)
  end
```

```ruby:lib/devise/controllers/sign_in_out.rb
      # Sign in a user that already was authenticated. This helper is useful for logging
      # users in after sign up. All options given to sign_in is passed forward
      # to the set_user method in warden.
      # If you are using a custom warden strategy and the timeoutable module, you have to
      # set `env["devise.skip_timeout"] = true` in the request to use this method, like we do
      # in the sessions controller: https://github.com/plataformatec/devise/blob/master/app/controllers/devise/sessions_controller.rb#L7
      #
      # Examples:
      #
      #   sign_in :user, @user                      # sign_in(scope, resource)
      #   sign_in @user                             # sign_in(resource)
      #   sign_in @user, event: :authentication     # sign_in(resource, options)
      #   sign_in @user, store: false               # sign_in(resource, options)
      #
      def sign_in(resource_or_scope, *args)
        options  = args.extract_options!
        scope    = Devise::Mapping.find_scope!(resource_or_scope)
        resource = args.last || resource_or_scope

        expire_data_after_sign_in! #refer below

        if options[:bypass]
          ActiveSupport::Deprecation.warn(<<-DEPRECATION.strip_heredoc, caller)
          [Devise] bypass option is deprecated and it will be removed in future version of Devise.
          Please use bypass_sign_in method instead.
          Example:
            bypass_sign_in(user)
          DEPRECATION
          warden.session_serializer.store(resource, scope) #refer below
        elsif warden.user(scope) == resource && !options.delete(:force)
          # Do nothing. User already signed in and we are not forcing it.
          true
        else
          warden.set_user(resource, options.merge!(scope: scope))
        end
      end

```

Array#extract_options! で Rails API のように柔軟な引数を取るメソッドを定義する
http://www.techscore.com/blog/2012/12/25/arrayextract_options-%E3%81%A7-rails-api-%E3%81%AE%E3%82%88%E3%81%86%E3%81%AB%E6%9F%94%E8%BB%9F%E3%81%AA%E5%BC%95%E6%95%B0%E3%82%92%E5%8F%96%E3%82%8B%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E3%82%92/



```ruby:lib/devise/controllers/sign_in_out.rb
     def expire_data_after_sign_in!
        # session.keys will return an empty array if the session is not yet loaded.
        # This is a bug in both Rack and Rails.
        # A call to #empty? forces the session to be loaded.
        session.empty?
        session.keys.grep(/^devise\./).each { |k| session.delete(k) }
      end

      alias :expire_data_after_sign_out! :expire_data_after_sign_in!
```

sessionが読み込まれないとsession.keysは空の配列を返してくる。
これはRackとRailsのバグだから、一回読み込んでやる必要がある。
そのためのsession.empty?だと。。

ただ単にsession消してるだけかな。


●warden.session_serializer.store(resource, scope)

```ruby:lib/warden/proxy.rb
    # Points to a SessionSerializer instance responsible for handling
    # everything related with storing, fetching and removing the user
    # session.
    # :api: public
    def session_serializer
      @session_serializer ||= Warden::SessionSerializer.new(@env)
    end
```


```ruby:lib/warden/session_serializer.rb
# encoding: utf-8
# frozen_string_literal: true
module Warden
  class SessionSerializer
    attr_reader :env

    def initialize(env)
      @env = env
    end

    def key_for(scope)
      "warden.user.#{scope}.key"
    end

    def serialize(user)
      user
    end

    def deserialize(key)
      key
    end

    def store(user, scope)
      return unless user
      method_name = "#{scope}_serialize"
      specialized = respond_to?(method_name)
      session[key_for(scope)] = specialized ? send(method_name, user) : serialize(user)
    end

    # 中略

  end # SessionSerializer
end # Warden
```

親玉みたいのが出てきた笑
storeもここに載ってる。
なんかsession機能の根幹になってそう。。

storeは、大まかに言うと
`session[:scope] = user`
を担ってるのね。実際にscope、userを入れているわけではないけど。


●warden.userについて

```ruby:lib/warden/proxy.rb
    def user(argument = {})
      opts  = argument.is_a?(Hash) ? argument : { :scope => argument }
      scope = (opts[:scope] ||= @config.default_scope)

      if @users.has_key?(scope)
        @users[scope]
      else
        unless user = session_serializer.fetch(scope)
          run_callbacks = opts.fetch(:run_callbacks, true)
          manager._run_callbacks(:after_failed_fetch, user, self, :scope => scope) if run_callbacks
        end

        @users[scope] = user ? set_user(user, opts.merge(:event => :fetch)) : nil
      end
    end
```

● warden.set_userについて

```ruby:lib/warden/proxy.rb
    # Manually set the user into the session and auth proxy
    #
    # Parameters:
    #   user - An object that has been setup to serialize into and out of the session.
    #   opts - An options hash.  Use the :scope option to set the scope of the user, set the :store option to false to skip serializing into the session, set the :run_callbacks to false to skip running the callbacks (the default is true).
    #
    # :api: public
    def set_user(user, opts = {})
      scope = (opts[:scope] ||= @config.default_scope)

      # Get the default options from the master configuration for the given scope
      opts = (@config[:scope_defaults][scope] || {}).merge(opts)
      opts[:event] ||= :set_user
      @users[scope] = user

      if opts[:store] != false && opts[:event] != :fetch
        options = env[ENV_SESSION_OPTIONS]
        if options
          if options.frozen?
            env[ENV_SESSION_OPTIONS] = options.merge(:renew => true).freeze
          else
            options[:renew] = true
          end
        end
        session_serializer.store(user, scope)
      end

      run_callbacks = opts.fetch(:run_callbacks, true)
      manager._run_callbacks(:after_set_user, user, self, opts) if run_callbacks

      @users[scope]
    end
```

あとでつづきかく