🦓

[Rails]DeviseのStoreLocationモジュール

2023/09/13に公開

はじめに

Deviseでログイン/ログアウト後のリダイレクト先をログイン/ログアウトする前のページにするにはStoreLocationモジュールを使います。

https://github.com/heartcombo/devise/wiki/How-To:-Redirect-back-to-current-page-after-sign-in,-sign-out,-sign-up,-update

実際どうやって動いているまで理解出来てないのでソースコードを読んでいきます。
https://github.com/heartcombo/devise/blob/079ed3b6f8b671acde2dd630d28d21adb010fb3a/lib/devise/controllers/store_location.rb


StoreLocationモジュール

1. 特定のスコープ(通常はDeviseのリソース)に関連付けられたセッションキーを生成する

def stored_location_key_for(resource_or_scope)
  scope = Devise::Mapping.find_scope!(resource_or_scope)
  "#{scope}_return_to"
end

resource_or_scope パラメータのスコープを特定: resource_or_scope は、Deviseのスコープ名またはリソース名を表します。
与えられた resource_or_scope をもとに、関連するスコープを特定します。例えば、:user というスコープが与えられた場合、scope 変数には "user" が格納されます。

"#{scope}_return_to"は、特定のスコープに関連付けられたセッションキーを生成します。

スコープ名をもとに、リダイレクト先URLをセッション内で識別するための一意のセッションキーを生成します。

session_key = stored_location_key_for(resource_or_scope)

一般的に、スコープ名に "_return_to" を結合することによって、セッションキーが作成されます。例えば、スコープ名が "user" の場合、セッションキーは "user_return_to" となります。

生成されたセッションキーは、セッション内でリダイレクト先URLを保存するために使用されます。
これにより、異なるスコープごとに異なるリダイレクト先URLを区別できます。

スコープ毎にリダイレクトURLを設定できると便利ですね。

session["#{resource/scope}_return_to"]
session["user_return_to"] = '/'
session["admin_return_to"] = '/admin'

2. 提供されたlocation文字列をURIオブジェクトに正規化する

uri = parse_uri(location)
def parse_uri(location)
   location && URI.parse(location)
rescue URI::InvalidURIError
   nil
end

引数として渡された location をURIオブジェクトにパースし、その結果を返します。
URIが無効な場合、URI::InvalidURIError が発生したときに nil を返します。
URIを正しくパースすることにより、無効なURIが渡された場合でもエラーを回避し、正確なURIオブジェクトを取得できます。

3. 特定のスコープに関連付けられたセッション内にリダイレクト先の場所(URL)を保存する

def store_location_for(resource_or_scope, location)
  session_key = stored_location_key_for(resource_or_scope) # 関連するスコープを特定する
  uri = parse_uri(location) # リダイレクト先のURLを解析する
  if uri
     path = [uri.path.sub(/\A\/+/, '/'), uri.query].compact.join('?')
     path = [path, uri.fragment].compact.join('#')
     session[session_key] = path # セッション内に保存する
   end
end

パスが正規化され、先頭に余分なスラッシュがある場合に削除されます。また、クエリとフラグメントが含まれている場合、それらが適切にパスに追加されます。

  1. uri.path.sub(/\A\/+/, '/'):

    • uri.path は与えられたURIのパス部分を表します。
    • .sub(/\A\/+/, '/') は、パスの先頭にある1つ以上のスラッシュ(/)をすべて単一のスラッシュ(/)に置換する処理を行います。
    • これにより、不要な連続したスラッシュが削除され、正規化されたパスに変換されます。
  2. uri.query:

    • uri.query は与えられたURIのクエリ文字列を表します。クエリ文字列は通常 ? で始まり、キーと値のペアが key=value の形式で含まれます。
  3. .compact.join('?'):

    • .compact メソッドは、配列内の nil 要素を削除します。クエリ文字列が存在しない場合、uri.querynil となり、これを削除しておきます。
    • .join('?') は、クエリ文字列を取得した配列要素を ? で結合し、最終的なクエリ文字列を生成します。
  4. uri.fragment:

    • uri.fragment は与えられたURIのフラグメント(またはアンカー)部分を表します。フラグメントは通常 # で始まり、ページ内の特定のセクションへのリンクに使用されます。

与えられたURIから正規化されたパス、クエリ文字列、およびフラグメントを生成し、それらを結合して新しいパスを作成する処理を行っています。

指定されたスコープに関連付けられたセッションキーを使用して、整形されたリダイレクト先URLをセッション内に保存します。

4. 特定のスコープに関連付けられたセッション内に保存されたリダイレクト先URLを取得する

def stored_location_for(resource_or_scope)
  session_key = stored_location_key_for(resource_or_scope)

  if is_navigational_format?
     session.delete(session_key)
  else
     session[session_key]
  end
end

与えられた resource_or_scope に関連付けられたリダイレクト先URLを取得し、必要に応じてセッション内で保存された情報を削除します。

stored_location_key_for(resource_or_scope) メソッドを呼び出して、resource_or_scope に関連付けられたセッションキー(リダイレクト先URLを保存するためのキー)を取得します。

def is_navigational_format?
  Devise.navigational_formats.include?(request_format)
end

is_navigational_format? のチェック: このメソッドは、is_navigational_format? メソッドを呼び出して、現在のリクエストがナビゲーション形式(HTMLなどの通常のページ遷移)かどうかを確認します。

リダイレクト先URLの取得または削除: もし現在のリクエストがナビゲーション形式の場合、session_key に関連付けられたセッション情報を削除します。
なぜなら、ユーザーは既にリダイレクトされ、次のページに遷移したからです。その後のリクエストで同じURLに再度リダイレクトする必要はないためです。
セッション内にリダイレクト先URLを保持し続けると、セッションデータが肥大化し、不要な情報が残り続ける可能性があるため、削除することが望ましいです。

ナビゲーション形式でない場合(APIなどでのリクエストの場合)、session_key に関連付けられたセッション情報を取得し、リダイレクト先URLを返します。

ログイン・ユーザー登録後のリダイレクト先のURLを指定する

def after_sign_in_path_for(resource_or_scope)
  stored_location_for(resource_or_scope) || super
end
def after_sign_up_path_for(resource_or_scope)
  stored_location_for(resource_or_scope) || super
end

ユーザーが以前にアクセスしていたページがセッションに保存されている場合、そのページへのリダイレクトを行います。そうでない場合、親クラスのデフォルトのリダイレクト先を使用します。

https://github.com/heartcombo/devise/blob/eed641d2bea11839ab13e943660da41cad14314d/lib/devise/controllers/helpers.rb

終わりに

ログイン/ログアウト後の挙動に関して理解が足りない部分も理解していきたいと思います。

Discussion