【devise_token_auth】sessions_controllerの@resource解説①【sign_in】

rails

本記事では、devise_token_authのsignin(sessionsコントローラのcreateアクション)時において取得される@resourceについて解説していきます。

ソースコードの動きを一つ一つ解説していきますので、deviseの処理について知りたい方はぜひ参考にしていただければと思います。

@resourceで何が取れるかだけ知りたい方はまとめに要点が書いてあるので、まとめを見てください!

sessions_controllerのソースコードについて

devise_token_authのsessions_controllerのソースコードはこちらになります。

@resourceを取得するコードを一つずつ解説していく

createアクションの処理の全体像はこちらになります。その中でも、下記の部分を取得していきます。

def create
############################# ここから #############################
  if field = (resource_params.keys.map(&:to_sym) & resource_class.authentication_keys).first
    q_value = get_case_insensitive_field_from_resource_params(field)

    @resource = find_resource(field, q_value)
  end
############################# ここまで #############################
  if @resource && valid_params?(field, q_value) && (!@resource.respond_to?(:active_for_authentication?) || @resource.active_for_authentication?)        valid_password = @resource.valid_password?(resource_params[:password])
    if (@resource.respond_to?(:valid_for_authentication?) && !@resource.valid_for_authentication? { valid_password }) || !valid_password
      return render_create_error_bad_credentials
    end

    create_and_assign_token

    sign_in(@resource, scope: :user, store: false, bypass: false)

    yield @resource if block_given?

    render_create_success
  elsif @resource && !Devise.paranoid && !(!@resource.respond_to?(:active_for_authentication?) || @resource.active_for_authentication?)
    if @resource.respond_to?(:locked_at) && @resource.locked_at
      render_create_error_account_locked
    else
      render_create_error_not_confirmed
    end
  else
    hash_password_in_paranoid_mode
    render_create_error_bad_credentials
  end
end

(14行目)if field ….

まずは、最初の処理から

# (1) 14行目
if field = (resource_params.keys.map(&:to_sym) & resource_class.authentication_keys).first
    q_value = get_case_insensitive_field_from_resource_params(field)

    @resource = find_resource(field, q_value)
end

まずは、resource_paramsから何をしているかを追っていきます。

※本記事では、ソースコードをどんどん追っていくので、どこのコードかわからなくなってしまうため、番号を振っています((1)でわからないコードがあれば(1-1)、(1-1)でわからないコードがあれば(1-1-1)で探すみたい感じで)。

(1-1)resource_paramsの定義場所

resource_paramsは下記で定義されています。

# (1-1) 129行目
def resource_params
  params.permit(*params_for_resource(:sign_in))
end

この、params_for_resourceについては、sign_up編で紹介していますのでそちらを参考にしてください!

params_for_resourceでは、引数で渡されたリソースのストロングパラメータに沿ったparamsを返します。

ですので今回は、sign_in時に許可するparamsが返されています。resource_params.keys.map(&:to_sym)では、渡されたkeyを配列にしています。今回だと、下記のようになるかなと思います。

resource_params.keys.map(&:to_sym) = [:email, :password, :remember_me]

(resource_class.authentication_keys).first

続いて、resource_class.authentication_keysについて。

resource_classについて、解説している記事があるのでそちらをご確認ください。

resource_class.authentication_keysは、ログイン時に必要な情報で、configファイルを触っていなければemailになっています。

if field = (resource_params.keys.map(&:to_sym) & resource_class.authentication_keys).first

ここで、if文に戻ると、&で積集合になっています(積集合とは、a,bの二つの配列がある場合にc=a & bとしたとき、cにはaとbのどちらの配列にも存在する値のみを抽出する)。

今回は、どちらにも[:email]が含まれており、firstメソッド(配列の1番最初の要素を取ってくる。今回はemailしかないからemailが取得される)によりfield=:emailとなります。

続いて、

q_value = get_case_insensitive_field_from_resource_params(field)

こちらですが、get_case_insensitive_field_from_resource_paramsが不明なので調査します。

(1-2)get_case_insensitive_field_from_resource_params

get_case_insensitive_field_from_resource_paramsを見ていきます。

# (1-2)7行目
  def get_case_insensitive_field_from_resource_params(field)
    # honor Devise configuration for case_insensitive keys
    q_value = resource_params[field.to_sym]

    if resource_class.case_insensitive_keys.include?(field.to_sym)
      q_value.downcase!
    end

    if resource_class.strip_whitespace_keys.include?(field.to_sym)
      q_value.strip!
    end

    q_value
  end

@q_valueですが、resource_paramsには、sign_inのparams(:email, :password, :remember_me)が入っています。そして、[field.to_sym]で、fieldではemailを受け取っているので、q_valueにはemailの入力値が入ります。

続いて、resource_class.case_insensitive_keysですが、これは、大文字と小文字を区別せずに扱う設定を使用しています。これは、configファイルで設定できますが、何も設定していない場合は[:email]となっています。

mattr_accessor :case_insensitive_keys
@@case_insensitive_keys = [:email]

条件に当てはまる場合、q_value.downcase!でemailのparamsが小文字に置換されます。

続いて、resource_class.strip_whitespace_keysですが、これはホワイトスペースを取り除くかの設定をしています。基本的に、authenticate_keysを設定したものをconfigファイルで設定しておく必要があります。こちらもデフォルトだと[:email]となっています。

mattr_accessor :strip_whitespace_keys
@@strip_whitespace_keys = [:email]

条件に当てはまる場合、q_value.strip!で文字列の前後のホワイトスペースを取り除きます

以上の処理が行われたq_valueが返り値として返されます。よって、get_case_insensitive_field_from_resource_paramsでは、認証用のparamsを成形する処理を行っています。

(1-3)@resource = find_resource(field, q_value)

最後に、上記の処理について調査します。まず、find_resourceメソッドは下記の様になっています。

# (1-3) 22行目
 def find_resource(field, value)
    @resource = if database_adapter&.include?('mysql')
                  # fix for mysql default case insensitivity
                  field_sanitized = resource_class.connection.quote_column_name(field)
                  resource_class.where("BINARY #{field_sanitized} = ? AND provider= ?", value, provider).first
                else
                  resource_class.dta_find_by(field => value, 'provider' => provider)
                end
  end

ここでは、database_adapterというものが定義されているので、それを確認します。

(1-3-1)database_adapterの調査
# (1-3-1) 32行目
  def database_adapter
    @database_adapter ||= begin
      rails_version = [Rails::VERSION::MAJOR, Rails::VERSION::MINOR].join(".")

      adapter =
        if rails_version >= "6.1"
          resource_class.try(:connection_db_config)&.try(:adapter)
        else
          resource_class.try(:connection_config)&.try(:[], :adapter)
        end
    end
  end

@database_adapter ||= beginは値が無ければ取りに行くので、そのまま進みます。

rails_version = [Rails::VERSION::MAJOR, Rails::VERSION::MINOR].join(“.”)はrailsのバージョンを撮りに行っています。6.1とか6.2とか。つぎのif文のadapterの条件式で使用されます。

そして、つぎのresource_class.try(:connection_db_config)ですが、これは、railsの情報になります。resource_classはdeviseが紐づいているモデルになる(Userが一般的)ので、rails cで確認することができます。

User.connection_db_config
 =>
 @configuration_hash={:adapter=>"postgresql", :encoding=>"utf8", :pool=>5, :host=>"db", :username=>"aaaaaa", :password=>"aaaaaa", :database=>"eb_development"},
 @env_name="development",
 @name="primary">

こんな感じで取得できると思います(rails version 6.1以上の場合)。adapterを確認すると、postgresqlになっていますね。この値が(1-3-1)のdatabase_adapterの返り値として返ります。

先ほどのif文に戻ると、include?(mysql)となっているので、elseの方に進みます。

(1-3-2)resource_class.dta_find_by(field => value, ‘provider’ => provider)
# (1-3-2)
resource_class.dta_find_by(field => value, 'provider' => provider)

ここで、fieldは:email、valueはログイン時に入力したemailの値になります。

また、providerはemailとなっています。

# 55行目(1-3-2-1)
  def provider
    'email'
  end

また、dta_find_byもdevise内で定義されており、下記の様になっています。

 def dta_find_by(attrs = {})
      find_by(attrs)
    rescue Mongoid::Errors::DocumentNotFound
      nil
    end

要は、email: “emailの値”、’provider’: emailとなる値をデータベースから取得しています。ログインしているインスタンスを取得しているというわけです。これが(1-3)の@resourceの結果となります!

まとめ

上記より、@resourceは入力したemailの値をデータベースから探して、該当するデータを返すものだということがわかりました!

今後、アレンジとかする際にどこのソースコードを変えるべきか悩んだ場合にこの記事を振り返って確認しようと思います!

コメント

タイトルとURLをコピーしました