【前置き】私の超限定的なSessionの使い方
ユーザ登録を5ページぐらいに分割して、最後のページまで入力したら登録が完了するようにする!と言うのをsessionメソッドを使って実現しました。その時の内容の備忘録。
イメージはこんな感じ。最後完了まで行ってから@user.saveしたい↓
※ 某フリーマケットコピーサイト作りの一環です笑
なお、実際の認証機能には'devise'を利用しています。そのため、deviseをオーバーライドさせて記載をしています。オーバーライド方法はこちら↓を参照しました。
【参考】「Rails で Devise のコントローラーをカスタマイズ」
セッションとは?
ステートフルな通信を実現するための仕組み。
その前にいつも使っている仕組み(ステートレス)の概念を理解する。
セッションを説明する前にRailsで普段からよく使っている、馴染みの通信を振り返りましょう。Railsをコーディングする際、ページ遷移やデータの受け渡しをするのによく使っている通信がありますよね?覚えていますか?
そう、HTTP通信です!
思い出せないアナタ!クライアント〜サーバ間のデータの受け渡しの時、
・viewでform_forにPOSTメソッド仕込んだ記憶ありますよね?
・controller側でパラメータとして送られてきたparams[:hoge]を取得したことありますよね?
アイツらはHTTP通信の仕組みで実現されています。何故こいつじゃダメなのか?
このHTTP通信。非常に便利な仕組みですが、ステートレスな通信と呼ばれています。
どういうことかと言うと、過去の通信内容を保持しない。と言うことです。常にまっさらな状態で通信が行われます。そのため、5ページ目では1ページ目のデータは消失していて使えません!
この概念について非常にわかりやすかったので、こちらに任せて内容割愛します。
いっそ、この後の説明もこっちの方がわかりやすい笑
【参考】Session管理
### ステートレスだと面倒臭いこと。
このステートレス通信だけでは、ちょっと面倒臭いことになる場面があります。
例えば、ログイン認証やお買い物かごなどの機能です。前の通信を忘れられてしまったら、毎回「ログイン認証する」や「欲しい商品が増えるたび過去に入れた商品も含めて全部を籠に入れ直す」などの処理を行う必要があります。非常に不便。
例えるなら、AmazonEchoに対し日常会話の成立試みて「よく分かりません」と言われてイラっとする時に近いでしょうか?(最近は進化しているのかもしれませんが)
その不便さを解消するために用意されたのが、ステート”フル”(状態を維持する)な通信を実現する”セッション”です。
### RailsのSessionって。
Railsアプリケーションではユーザごとにセッションが設定されます。
その中に、前のリクエストの情報を次のリクエスでも利用できるよう小さなデータを保存することができます。
このセッションデータの保存先には幾つか種類がありますが、デフォルトではクライアント側のブラウザCoockieに保存する方法が取られます。
なお、Coockieは暗号化され、保存形式もハッシュを模しており扱いやすいです。
今回私もデフォルト設定状態でsessionを利用しました。
そして、このセッションの保存領域を簡単に制御できるように用意されたのがSessionメソッドなるものです。(多分)
Sessionメソッドの使い方
大前提としてrailsで提供されるsessionデータはviewsとcontrollerでのみ利用可能です。
modelなどでは直接呼び出せないらしいです。
非推奨事項
「小さなデータ」と前おいた通りセッション利用において、利用するセッションストアの種類に関わらず大きいデータ(coockie利用時は4KBと言う制限がある)を扱うことは非推奨となっています。
また、セッションに複雑なオブジェクト (モデルインスタンスなどの基本的なRubyオブジェクトでないもの) を保存することはお勧めされていないようです。(問題を起こす可能性あり)
ただ、今回はモデルインスタンス使っちゃいますけど!笑
①Sessionにデータを入れる
極論。これでOK!!!!
項目名もなんでも自由につけれちゃいます。
session[:nickname] = params[:user][:nickname]
ですが、これでは項目数が増えてきたときに面倒臭いことに成りそうですね。
なので2段ネストしたく成ります。この瞬間ある制約が発生します。
それは「モデルと紐づける」必要がある、と言う制約です。(関連記事見つけられず、経験知です)
参考として下図にOK例とNG例と言う形でコーディング例を書いてみました。
# OK例
session[:user] = User.new()
session[:user][:nickname] = "はむ子"
session[:user] = user_params #user_paramsは私が作ったストロングパラメータ取得用メソッド
# NG例(モデル紐付け前→NoMethodError: undefined method `[]' for nil:NilClass)
session[:user][:nickname] = "はむ子"
# NG例(モデルに無い項目→ActiveModel::MissingAttributeError: can't write unknown attribute 'itazura')
session[:user] = User.new()
session[:user][:itazura] = "はむすけ"
②Sessionのデータを参照する
データを参照するときに一癖あります。
coockieの仕様上らしいのですが、ネスト2段目以降はシンボル型の指定ができなくなります。
こちらもコーディングサンプルを記載。
# OK例
@user[:user][:tel] = session[:user]["tel"]
# NG例(シンボル型で入れてみる→NoMethodError: undefined method `[]' for nil:NilClass)
@user[:user][:tel] = session[:user][:tel]
// OK例
= f.text_field :nickname, value:"#{session[:user]["nickname"]}"
// NG例
= f.text_field :nickname, value:"#{session[:user][:nickname]}"
###③sessionデータを更新する
注意が必要なのは、データを被せても追記でなく洗い替えになってしまう点。
そのため、更新方法は以下2パターンのどちらかを使うのが良いのかなと思いました。
下記にプログラミング例を記述します。
# OK例① ※user_paramsは私が作ったストロングパラメータ取得用メソッド
session[:user]["tel"] = user_params[:tel]
session[:user]["email"] = user_params[:email]
# OK例② ※値更新されるけど、rollbackも走るから少し気持ち悪い
session[:user].update(user_params[:tel],user_params[:email])
# NG例 → 元々あった項目の値が全て消えます!!要注意!!!
session[:user] = user_params
###④Sessionデータを削除(解放)する
一度設定したSessionデータをクリアする方法はいくつかあります。
下記にプログラミング例を記述します。
# パターン① 私はこれを使いました。
session[:user].clear
# パターン②
session[:user] = nil
# パターン③
session.delete(:user)
色々なパターンがありますが、注意事項としては項目指定しないと全てのセッションが無くなります。
例えば、deviseのログインセッションの情報とか。
削除の時には、それを念頭に置いて精細かつ大胆に行きましょう。
##Sessionデータの削除タイミングについて
ここで問題です。
一度保存したsessionデータは、どのタイミングで削除されるでしょうか?
前提:MacBook利用でブラウザはChromeでデフォルト設定のまま
A:開いているタブを閉じたとき
B:開いているブラウザを全て閉じたとき
C:MacBookを再起動したとき
D:明示的に削除する(又は有効期限切れになる)まで消えない
・
・
・
・
・
・
・
・
・
・
・
・
答えは・・・Dです!!!(Cでも消える時もありましたが・・・)
sessionの説明文では、ブラウザを閉じたときに消えると言う記事をよく見かけます。
ですが、この動きは使っているブラウザソフトの仕様や設定により異なるようです。
要するにここで何が伝えたいかと言うと、
「ブラウザを閉じてもクライアント上にデータが取り残されている可能性がある」
⇩
「セキュリティ的にリスクがある!」
と言うことです。そのため、勝手に無くなるのを待つのではなく、
Sessionデータは、不要になったら明示的に削除してあげるのが適切だと私は思いました。
まとめ
セッションは不要になったら、ちゃんと消してあげましょう!
番外編
sessionデータをUserモデルとaddressモデルに実際に保存した時の書き方。
# 現状の書き方はuserとaddrressがhas_oneの場合の書き方です。
# has_manyだった場合は@user.address.buildらしい
@user = User.new(session[:user])
@user.build_address(session[:address])
@user.save
セッションを勉強するのに
普段'devise'と言うgemを使っているとsessionに触れる機会は少ないですよね。
セッションを勉強するのにおすすめなのは、Railsチュートリアルの第8章をやってみるといいらしいです!
(私は未着手ですが・・・)
Railsチュートリアル第8章:
https://railstutorial.jp/chapters/log_in_log_out?version=4.2#sec-sessions_and_failed_login
参考文献
Railsガイド:
https://railsguides.jp/action_controller_overview.html#%E3%82%BB%E3%83%83%E3%82%B7%E3%83%A7%E3%83%B3
Railsセキュリティガイド:
https://railsguides.jp/security.html
コピーサイトでユーザ登録を実現するのに参考になるページ:
https://qiita.com/ATORA1992/items/40fc543742a6df5a17c1#