RailsでAction Controllerについて学んでみた
はじめに
このコラムは、Ruby初心者の著書が、Railsガイドに沿って、リアルタイムで勉強をしていくコラムです。全12回を予定しています。勉強する上でつまずいた点やその回避法、他のプログラミング言語や職業経験に基づいたアドバイスなども紹介する予定です。RubyやRailsに興味のある方は、ぜひ一緒に勉強してみませんか。
今回参照するガイドはこちらです。
Action Controller の概要
コントローラの命名規則
コントローラ名は「大文字始まりの複数形」+「Controller」になります。絶対に大文字始まりの複数形でないといけないわけではありませんが、デフォルトの機能をそのまま利用するためにも、特に理由がなければこの命名規則に従っておいた方が楽ですよね。
パラメータ
コントローラで受け取ることのできるパラメータには「GET」と「POST」の2種類がありますが、どちらもコントローラ内にて「params」という名前のハッシュでアクセスすることができます。
GETパラメータは、URLの一部として送信されるパラメータ(「?」より後ろの部分)で、「クエリ文字列パラメータ」と言う場合もあります。
(例:http://example.com/user?keyword=taro)
POSTパラメータは、通常はユーザーが記入したHTMLフォームから受け取るパラメータです。
例:
<form method="post" action="index.php"> <input type="text" name="keyword" value="taro"><!-- ブラウザ上で変更が可能 --> </form>
GETやPOSTについての詳しい説明をRailsガイドコラムの第一回で書いていますので、よろしければ復習がてらご覧くださいね。
ハッシュと配列のパラメータ
パラメータ名に「[]」を追加すると、パラメータを配列で受け取ることができます。例えば「GET /clients?ids[]=1&ids[]=2&ids[]=3」というパラメータを受け取ると、ハッシュのparams[:ids]は["1", "2", "3"]となります。
ここで使用している「[」や「]」の記号はURL用にエンコードするべき文字ですが、Railsのテンプレートではそのまま「[]」と書いて問題ありません。なぜなら、パラメータの送信時にブラウザが「/clients?ids%5b%5d=1&ids%5b%5d=2&ids%5b%5d=3」のように自動で変換し、また受信したRailsも自動的にこれを復元してくれるからです。
ネットワークで通信する都合上、パラメータの値はすべて「文字列型」になります。「GET /clients?id=100」と送信すると、params[:id]は数値の「100」ではなく文字列の"100"になります。必要であれば、パラメータの数字をto_iメソッドで整数に変換しておきましょう。
params の中に「[]」、「[nil]」、「[nil, nil, ...]」などの値があると、それらはすべて自動的にnil に置き換えられます。これにより、要素数が3ある配列の中身が実はすべて空だったというようなことがなくなるので、nilチェックの際などに非常に便利です。
JSONパラメータ
Railsでは、リクエストヘッダの「Content-Type」に「application/json」が指定されていると、自動的にパラメータをparamsハッシュに変換してくれます。
ルーティングパラメータ
paramsハッシュに必ず含まれるキーに「:controller」と「:action」がありますが、これらの値には直接アクセスせず、「controller_name」と「action_name」という専用のメソッドを使用してアクセスするようにします。
また、ルーティング(URL)のどの部分が、どのパラメータに対応するかという設定をすることができます。
ルーティングの設定
get '/clients/:status' => 'clients#index', foo: 'bar'
アクセスするURL
/clients/active
取得できるパラメータ
params[:action] => indexparams[:status] => activeparams[:foo] => bar これは、ルーティング未設定で次のURLへのアクセスした場合と同じです。 /clients/index?status=active&foo=bar
Strong Parameters
一番初めのガイド(はじめに > Rails をはじめよう > 5.6 コントローラでデータを保存する)にこの「Strong Parameters」が出てきましたが、みなさん記憶されているでしょうか。
フォームから送信されたデータを、そのままノーチェックでデータベースに登録することを「マスアサインメント」といいますが、これはとても簡単な仕組みです。登録したい項目を増やしたければ、テンプレートのHTMLにフォーム要素を追加するだけで済みます。しかしこれではとてもセキュリティの低い仕組みになってしまいます。なぜならば、HTMLや送信パラメータは、少し技術のある人ならば誰でも自由に書き換えることができるため、更新されると困る項目まで更新されてしまう可能性があるからです。
これに対し、更新してもいい項目、必須入力の項目などを予め指定しておく方法を、「Strong Parameters」といいます。
マスアサインメント
- コントローラが受け取ったパラメータをノーチェックでまるごと自動的にモデルに渡せるようにすること
- セキュリティが低い
Strong Parameters
- Action ControllerのパラメータがActive Modelのマスアサインメントに利用されることを禁止する
- 許可するパラメータを指定できる
- 必須にするパラメータを指定できる
Strong Parametersに関する詳細を、以下の項目で見ていきましょう。
許可されたスカラー値
登録可能な項目の許可は、「permit」で指定します。一方、入力必須のパラメータは「require」で指定します。「permit」と同時に指定が可能です。
コード | 意味 |
---|---|
params.permit(:id) | パラメータ「ID」を許可 |
params.permit(id: []) | パラメータ「ID(配列)」を許可 |
params.permit! | パラメータをすべて許可(セキュリティが低いので注意) |
params.require(:person).permit(:name, :age) | パラメータ「person」は必須、「name」「age」を許可 |
ネストしたパラメータ
多重階層のパラメータも指定できます。
params.permit(:name, { emails: [] }, friends: [ :name, { family: [ :name ]}])
上記例の場合、許可されるパラメータは以下のようになります。
params[:name] params[:emails][0] params[:emails][1] params[:friends][0][:name] …
セッション
インターネットにおける「セッション」とは、同じ人が一連の動作を行っているかどうかを判定するためのものです。アクセスしてきた人に対してセッションを開始し、サービスを利用している間はセッションを継続し、一定時間アクセスがなくなるとセッションを終了します。
Railsに限らず一般的なログイン処理を例に挙げると、まずログイン成功時にサーバーでセッションIDを発行し、そのIDをサーバーのデータベースとクライアントのクッキーに保存します。サーバーとクライアントのそれぞれに保存したセッションIDが一致する限り、ログイン状態が保たれるようになります。ログアウト時には両方のセッションを削除しますが、ログアウトする前にクッキーの有効期限が切れてしまい、再度ログインが必要になることもあります。クッキーの有効期限は、サーバー側で自由に設定することができます。
Railsのセッションは、コントローラとビューで使用できます。セッションの保存先(ストレージ)は、以下の表の中から選べます。
種類 | 特徴 |
---|---|
ActionDispatch::Session::CookieStore | ブラウザのcookieに保存する 約4KBのデータを保存可能(セッションに大量のデータを保存することは良くないため、これで十分) 非常に軽量 新規Webアプリケーションでセッションを利用するための準備が不要 |
ActionDispatch::Session::CacheStore | データをRailsのキャッシュに保存する キャッシュなので消える可能性もある 用途 ・ユーザーセッションに重要なデータが含まれていない場合 ・ユーザーセッションを長期間保存する必要がない場合(flashメッセージで使用したいだけの場合など) |
ActionDispatch::Session::ActiveRecordStore | Active Recordを使用してデータベースに保存する(activerecord-session_store gemが必要) |
ActionDispatch::Session::MemCacheStore | データをmemcachedクラスタに保存する(この実装は古いのでCacheStoreを検討すべき) |
Railsでは、CookieStoreがデフォルトかつ推奨のセッションストアです。別のセッションメカニズムが必要な場合は、config/initializers/session_store.rb ファイルを変更することで切り替えます。
他人のセッションIDを使うとログインできたり(セッションの乗っ取り)、関連するデータを盗めたりしますので、セッションの取り扱いには注意が必要です。RailsではセッションIDをURLで渡すことは、セキュリティ上の危険があるため許可されません。アクセスしたことのあるURLはブラウザの記憶に残りますし、閲覧先サイトのサーバーでは直前にアクセスしたURLを知ることができるため、URLは第三者にばれやすいのです。よって、セッションIDはURLではなく、cookieで渡さなくてはなりません。
Flash
フラッシュには、「一瞬の、束の間の」という意味があります。Railsアプリケーションではflashを使用して警告や通知を表示するのが通例で、それらは「フラッシュメッセージ」と呼ばれます。
flashに値を追加すると、直後のリクエストでのみその値を利用できます。
class LoginsController < ApplicationController def destroy session[:current_user_id] = nil・・・(1) flash[:notice] = "正常にログアウトしました。"・・・(2) redirect_to root_url・・・(3) end end
(1)ログアウト処理(nilを指定してセッションを削除しています)
(2)flashメッセージを設定
(3)root_urlにリダイレクト(これが「直後のリクエスト」)
↓
root_urlのテンプレートが、上記flashメッセージの内容を表示します。
root_urlのテンプレート
<% flash.each do |name, msg| -%> <%= content_tag :div, msg, class: name %> <% end -%>
flashメッセージとリダイレクトは、以下のように一行にまとめて書くこともできます。
redirect_to root_url, notice: "正常にログアウトしました。"
flash.now
次のリクエストではなく、現在のリクエストでflashメッセージを使うこともできます。
class ClientsController < ApplicationController def create @client = Client.new(params[:client]) if @client.save # ... else flash.now[:error] = "Could not save client" render action: "new" end end end
保存処理に失敗したら、リダイレクトせずに元の入力画面を表示したいため、「次のリクエスト」が起こらないという例ですね。
Cookies
クッキーは、ブラウザが備えるデータ保存領域(ストレージ)です。以下の使用例では、以前に入力したフォーム値を再現しています。
class CommentsController < ApplicationController def new # cookieに投稿者名が残っていたらフィールドに自動入力する @comment = Comment.new(author: cookies[:commenter_name]) end def create @comment = Comment.new(params[:comment]) if @comment.save flash[:notice] = "Thanks for your comment!" if params[:remember_name] # 投稿者名を保存する cookies[:commenter_name] = @comment.author else # 投稿者名がcookieに残っていたら削除する cookies.delete(:commenter_name) end redirect_to @comment.article else render action: "new" end end end
セッションを削除する場合は、キーにnilを指定しましたが、cookieを削除する場合は、この方法ではなく、「cookies.delete(:key)」を使用します。
クッキーはブラウザに保存されるので、ブラウザを使用している本人や、ブラウザを共有している第三者、はたまたブラウザのクッキーを取得できる他のWebサービスにデータを読まれる可能性があります。ユーザーに知られたくないデータ、本人以外に知られてはいけないデータは、暗号化をしておくといいでしょう。
ActionDispatch::Cookies::ChainedCookieJars
生のデータを保存する
cookies[:user_name] = "david"
暗号化して保存する
cookies.encrypted[:discount] = 45
暗号化されたクッキー
Set-Cookie: discount=ZS9ZZ1R4cG1pcUJ1bm80anhQang3dz09LS1mbDZDSU5scGdOT3ltQ2dTdlhSdWpRPT0%3D--ab54663c9f4e3bc340c790d6d2b71e92f5b60315; path=/
暗号化したクッキーの内容を見る
cookies.encrypted[:discount] # => 45
期限付きクッキーを保存する
cookies.permanent[:prefers_open_id] = true
期限が付いたクッキー
Set-Cookie: prefers_open_id=true; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
署名済みクッキーを保存する
cookies.signed[:discount] = 45
実際のクッキー
Set-Cookie: discount=BAhpMg==--2c1c6906c90a3bc4fd54a51ffb41dffa4bf6b5f7; path=/
署名済みクッキーの内容を見る
cookies.signed[:discount] # => 45
期限付きかつ署名済みクッキーを保存する
cookies.permanent.signed[:remember_me] = current_user.id
実際のクッキー
Set-Cookie: remember_me=BAhU--848956038e692d7046deab32b7131856ab20e14e; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
最後に
いかがでしたか? 他のプログラミング言語などでウェブサービスを作ったことのある方は、これらの機能がどのようなものか想像が付くと思いますが、そうでない方はやはり実際に何かを作ってみた方がいいですよね。百聞は一見に如かず。まだまだコントローラの章は続きますよ。それではまた!