RailsでAction Controllerについて学んでみた

2015年9月3日(木)
野田 貴子

はじめに

このコラムは、Ruby初心者の著書が、Railsガイドに沿って、リアルタイムで勉強をしていくコラムです。全12回を予定しています。勉強する上でつまずいた点やその回避法、他のプログラミング言語や職業経験に基づいたアドバイスなども紹介する予定です。RubyやRailsに興味のある方は、ぜひ一緒に勉強してみませんか。

今回参照するガイドはこちらです。

Action Controller の概要

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チェックの際などに非常に便利です。

Rails セキュリティガイド 8 安全でないクエリ生成

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::ActiveRecordStoreActive 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

最後に

いかがでしたか? 他のプログラミング言語などでウェブサービスを作ったことのある方は、これらの機能がどのようなものか想像が付くと思いますが、そうでない方はやはり実際に何かを作ってみた方がいいですよね。百聞は一見に如かず。まだまだコントローラの章は続きますよ。それではまた!

1983年生まれ。大学卒業後、ソフトウェア開発の営業を経て、ソフトウェア開発業務に転向。現在は自社パッケージのフロントエンド開発のほか、PHPでの受託開発案件、日→英のローカライズ案件などを担当。

連載バックナンバー

Think ITメルマガ会員登録受付中

Think ITでは、技術情報が詰まったメールマガジン「Think IT Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。

Think ITメルマガ会員のサービス内容を見る

他にもこの記事が読まれています