Actionコントローラーの概要

このガイドでは、コントローラーがどのように動作するか、どのようにアプリケーションのリクエストサイクルに適合するかを学びます。 このガイドを読むことで、次の事が学べるはずです。

  • コントローラーを通してリクエストのフローに従う方法について
  • コントローラーへのパラメータ渡しを制限する方法について
  • セッションまたはCookie内のデータ格納の方法とその理由について
  • リクエスト処理中のコード実行をフィルタリングしながら動作する方法について
  • Actionコントローラーの組み込みHTTP認証の使用方法について
  • ユーザーへのブラウザにストリームデータを直接送る方法について
  • アプリケーションのログに表示されないために、機密パラメータをフィルタする方法について
  • リクエスト処理中に発生するかもしれない例外の扱い方について

1. コントローラーは何をするか?

アクションコントローラーはMVCのCに当たる部分です。 リクエストのためにどのコントローラーを使用するかルーティングで決定された後、 コントローラーはリクエストの意図を汲み取る事と適切な出力を生成する事を請け負います。 幸運な事に、アクションコントローラーはあなたのためにほとんどの基本的な事を行ってくれて、 また可能な限り分かり易くしたスマートな規約を使用します。

ほとんどのRESTfulなアプリケーションは、 コントローラーはリクエスト(開発者からは隠蔽される)を受け取り、 モデルからデータの取得または保存を行い、HTML出力を生成するためにビューを使用します。 もし、あなたのコントローラーが少し異なる事を行う必要がある場合でも問題は無く、 これは、コントローラーを動かすための最も一般的な方法であるだけに過ぎません。

コントローラーは、モデルとビューの中間者として考えられています。 ビューはユーザーにデータを表示出来るように、ビューでモデルデータを利用可能にし、 ユーザーから送られてきたモデルのデータを保存または更新します。

ルーティング(経路)処理について詳しく知りたければ、 Railsのルーティングを参照してください。

2. コントローラー命名規約

Railsのコントローラーの命名規約は、コントローラー名の最後の単語が複数であることを推奨しますが、 必ずそうしなければいけないという事ではありません。(例: ApplicationController) 例えば、ClientsControllerが、ClientControllerよりも推奨され、 同様にSiteAdminsControllerは、SiteAdminControllerまたはSitesAdminsControllerよりも推奨されます。

この規約に従えば、:pathまたは:controllerによる調整無しで、 デフォルトの経路ジェネレーター(例: resources等)で、 一貫したURLとパスヘルパーを保持することが可能になります。 詳細はRailsによるレイアウトと描画を確認してください。

コントローラーの命名規約とモデルの命名規約は異なり、モデルは単数形が推奨されます。

3. メソッドとアクション

コントローラーは、ApplicationControllerから継承されたRubyクラスで、 他のクラスのようにメソッドを持ちます。 あなたのアプリケーションはリクエストを受け取ると、 ルーティングが実行するコントローラーとアクションを決定し、 Railsはコントローラーのインスタンスを作成し、アクションと同じ名前のメソッドを実行します。

class ClientsController < ApplicationController
  def new
  end
end

例として、あなたのアプリケーションの/clients/newに、新しい顧客を作成するためにユーザーがアクセスした場合、 RailsはClientsControllerのインスタンスを作成し、newメソッドを実行します。 上記の例のようにメソッドの中身が空でも、アクションで何も指定が無ければ、 Railsはデフォルトでnew.html.erbビューを描画するので問題ありません。 下記のようにnewメソッドは、新しいClientを作成することで、 @clientインスタンス変数をビューで利用可能にします。

def new
  @client = Client.new
end

この詳細については、Railsによるレイアウトと描画を参照してください。

ApplicationControllerは、多くの便利なメソッドが定義されたActionController::Baseを継承します。 このガイドでそれらの一部を説明しますが、あなたがそれらについて何なのかを詳しく知りたいのであれば、 APIドキュメント、またはそのソースコードで確認することが可能です。

publicメソッドだけが、アクションとして呼び出し可能です。 補助やフィルターのようなアクションではないメソッドの可視性を下げることは、良い慣習と言えます。

4. パラメータ

あなたは、コントローラーのアクション内でユーザーによって送られたデータまたはその他のパラメータに、アクセスしたいと考えているかもしれません。 Webアプリケーションで使用可能なパラーメタは2種類存在します。 1つ目のパラメータはURLの一部としておくられる、クエリー文字列パラメータと呼ばれるものです。 クエリー文字列は全てURL内の"?"の後に指定されます。 2つ目のパラメータは通常POStデータとして参照されるものです。 この情報は通常、ユーザーがHTMLフォームに情報を入力したものが送られてきます。 HTTPのPOSTリクエストの一部としてのみ送ることが可能なため、POSTデータと呼ばれています。 Railsはクエリー文字列とPOSTパラメータの区別をせず、両方ともコントローラー内のparamsハッシュで利用可能になります。

class ClientsController < ApplicationController
  # このアクションは、HTTPのGETリクエストによって実行されるため、
  # クエリー文字列パラメータを使用しますが、この場合はどちらのパラメータで
  # アクセスされても動作に違いはありません。
  # このアクションへの下記のようなURLは、
  # activated状態の顧客(client)の一覧を取得します。
  # /clients?status=activated
  def index
    if params[:status] == "activated"
      @clients = Client.activated
    else
      @clients = Client.inactivated
    end
  end

  # このアクションはPOSTパラメータを使用し、
  # ユーザーによって送信されるHTMLフォーム情報がほとんどです。
  # このRESTfulなリクエストではURLは"/clients"となり、
  # データはリクエストボディの一部として送信されます。
  def create
    @client = Client.new(params[:client])
    if @client.save
      redirect_to @client
    else
      # この行はデフォルトの"create"ビューを描画する処理を上書きします。
      render "new"
    end
  end
end

4.1 ハッシュと配列パラメータ

paramsハッシュには、1次元キーと値への制限はありません。 これには配列と(入れ子の)ハッシュを含めることが出来ます。 値の配列を送るには、空の角括弧のペアである"[]"をキー名に追加します。

GET /clients?ids[]=1&ids[]=2&ids[]=3

この例での実際のURLは、URLで"["と"]"は許可されないため、 "/clients?ids%5b%5d=1&ids%5b%5d=2&ids%5b%5d=3"としてエンコードされます。 ほとんどの場合、ブラウザとRailsがこれを受け取った際にデコードし直してくれるので、この事について心配する必要はないでしょう。 ただし、もし手動でそのようなリクエストを送信する必要がある場合は、このことに注意してください。

こうすることで、params[:ids]の値は、["1", "2", "3"]になります。 パラメータの値は常に文字列であり、Railsはそれらを型変換しないことに注意してください。

ハッシュを送信するには、括弧内にキー名を含めてください。

<form accept-charset="UTF-8" action="/clients" method="post">
  <input type="text" name="client[name]" value="Acme" />
  <input type="text" name="client[phone]" value="12345" />
  <input type="text" name="client[address][postcode]" value="12345" />
  <input type="text" name="client[address][city]" value="Carrot City" />
</form>

このフォームが送信されると、params[:client]の値は、
{ "name" => "Acme", "phone" => "12345", "address" => { "postcode" => "12345", "city" => "Carrot City" } }になります。 params[:client][:address]内のハッシュは、入れ子になっている事に注意してください。

paramsハッシュは、実際はActiveSupport::HashWithIndifferentAccessのインスタンスで、 ハッシュのように振る舞いますが、キーとしてシンボルと文字列を同じように使用出来るようにしてくれます。

4.2 JSONパラメーター

もし、あなたがWebサービスアプリケーションを作っているのであれば、 自身でJSONフォーマットでのパラメータを受け入れが快適であると感じたかもしれません。 Railsは自動的にあなたのパラメーターを、通常通りアクセス出来るparamsハッシュに変換します。

例として、もしあなたが下記のJSONコンテンツを送信した場合、

{ "company": { "name": "acme", "address": "123 Carrot Street" } }

{ "name" => "acme", "address" => "123 Carrot Street" }として、 params[:company]を取得します。

また、もしconfig.wrap_parametersをイニシャライザー内で変更、 またはコントローラー内でwrap_parametersを呼び出しているのであれば、 JSONパラメーターのroot要素を安全に省略することも可能です。 パラメータは複製され、デフォルトでコントローラー名に沿ったキー内にラップされます。 そのため、上記のパラメーターは下記のように書く事が可能で、

{ "name": "acme", "address": "123 Carrot Street" }

CompaniesControllerにデータを送信したとすると、 :companyキーで下記のようにラップされます。

{ name: "acme", address: "123 Carrot Street", company: { name: "acme", address: "123 Carrot Street" } }

APIドキュメントを参照することで、 ラップするキーまたは特定のパラメータの名前をカスタマイズすることができます。

XMLパラメータ解析のサポートは、actionpack-xml_parserというgem名で抜き取られています。

4.3 パラメータのルーティング

paramsは常に:controller:actionキーを含めますが、 これらの値にアクセスする変わりにcontroller_nameaction_nameメソッドを使用すべきです。 また、:idのようなルーティングによって定義されたその他のパラメータも利用可能です。 例として、アクティブと非アクティブのどちらかの顧客を表示出来る、顧客リストを表示するようなケースを考えてみます。 "綺麗な"URLで、:statusを捕捉出来るように経路に追加することが可能です。

get '/clients/:status' => 'clients#index', foo: 'bar'

この場合、ユーザーが/clients/activeのURLを開くと、params[:status]に"active"が設定されます。 この経路が使用されると、params[:foo]もまた、クエリー文字列に渡されたものとして"bar"が設定されます。 同様に、params[:action]には"index"が含まれます。

4.4 default_url_options

グローバルのデフォルトパラメーターを、コントローラー内でdefault_url_optionsと呼ばれるメソッドを定義する事で、 URL生成のために設定することが出来ます。 このメソッドは、希望するデフォルト値を持つキーが必ずシンボルであるハッシュを返さなければいけません。

class ApplicationController < ActionController::Base
  def default_url_options
    { locale: I18n.locale }
  end
end

これらのオプションはURL生成の元として使用され、 url_for呼び出しで渡されるオプションによって上書きされる可能性があります。

もし、ApplicationController内で、 上記の例のようにdefault_url_optionsを定義すると、 全てのURL生成で使用されます。 このメソッドは、その場所でのURL生成でしか作用しない、特定のコントローラー内でも定義する事が可能です。

4.5 Strongパラメーター

Strongパラメーターを指定することで、アクションコントローラーはそれらがホワイトリストに登録されない限り、 ActiveモデルのMass Assignmentsでの使用を禁止します。 これは、まとめて更新する事を許可する属性をあなたが選択しなければいけないことを意味し、 意図せず許可しない属性が晒されてしまうことを防ぎます。

加えて、パラメータは必須としてマークすることで、 予め定義したraise/resucueフローを通り、最終的に400のBad Requestで何も処理をしないようにすることが可能です。

class PeopleController < ActionController::Base

  # パラメータへの明確な許可をせずにmass assignmentを使用しているため、
  # ActiveModel::ForbiddenAttributes例外が発生します。
  def create
    Person.create(params[:person])
  end

  # これはパラメータ内のpersonキーが正しければ、正常に処理され、
  # そうでなければ、ActionController::ParameterMissing例外を発生させ、
  # ActionController::Baseにキャッチされて、400のBad Requestで応答します。
  def update
    person = current_account.people.find(params[:id])
    person.update_attributes!(person_params)
    redirect_to person
  end

  private
    # createとupdate間で同じ許可リストを再利用出来ることから、
    # プライベートメソッドを使用して、パラメータ許可をカプセル化する事は、
    # 良いパターン(慣習)と言えます。
    # また、ユーザー毎に許可属性のチェックにも、このメソッドを使用することが出来ます。
    def person_params
      params.require(:person).permit(:name, :age)
    end
end
4.5.1 許可されたスカラ値

下記の処理が与えられると、

params.permit(:id)

もし:idキーがparams内にあればホワイトリストをパスし、 関連する許可されたスカラ値を持つことになります。 そうでなければフィルターで除去され、配列、ハッシュ、またはその他のオブジェクトを入れることが出来ません。

許可されたスカラ値の型に出来るものには、 StringSymbolNilClassNumericTrueClassFalseClassDateTimeDateTimeStringIOIOActionDispatch::Http::UploadedFileRack::Test::UploadedFileがあります。

params内でその値を、許可された配列のスカラ値として宣言しなければいけない場合は、 キーを空の配列にマッピングします。

params.permit(id: [])

ハッシュパラメータ全体をホワイトリストにするのに、permit!メソッドを使用することが出来ます。

params.require(:log_entry).permit!

これは、:log_entryパラメータのハッシュとそのサブハッシュを許可するものとしてマークします。 現在の属性と今後mass-assignedとなる属性全てが許可される事になるため、 permit!の使用には、最大限の注意を払うべきです。

4.5.2 入れ子パラメータ

入れ子パラメータを下記のようにして許可することも可能です。

params.permit(:name, { emails: [] },
              friends: [ :name,
                         { family: [ :name ], hobbies: [] }])

これは、nameemailsfriends属性をホワイトリストとして宣言します。 emailsは許可されたスカラ値の配列で、 friendsは特定の属性を持つリソースの配列であることが期待されます。 配列のリソースは、name属性(許可されたスカラ値を指定可)、 許可されたスカラ値の配列としてのhobbies属性、 name(許可されたスカラ値を指定可)を持つように制限されたfamily属性を持つ必要があります。(翻訳に自信なし)

4.5.3 その他の例

newアクション内でも、許可された属性を使いたくなるかもしれません。 ただし、この場合には通常new呼び出し時には存在しないrootキー上で、 requireを使用できないという問題があります。

# fetchでデフォルトを提供することが可能で、
# そこからStrongパラメーターのAPIを使用します。
params.fetch(:blog, {}).permit(:title, :author)

accepts_nested_attributes_forは、関連するレコードの更新と削除を許可します。 下記は、id_destroyパラメータを元にしています。

#:idと:_destroyを許可
params.require(:author).permit(:name, books_attributes: [:title, :id, :_destroy])

integerキーのハッシュは扱いが異なり、それらが直接の子であるかのように宣言をすることが出来ます。 has_manyの関連付けと連携してaccepts_nested_attributes_forを使用すると、 これらのパラメータの種類を取得します。

# 下記のデータをホワイトリストにするには…
# {"book" => {"title" => "Some Book",
#             "chapters_attributes" => { "1" => {"title" => "First Chapter"},
#                                        "2" => {"title" => "Second Chapter"}}}}

params.require(:book).permit(:title, chapters_attributes: [:title])
4.5.4 Strongパラメータ外のスコープ

StrongパラメータAPIは、その他で多く使用されている共通した考えにに基いて設計されています。 これは、あなたのホワイトリストについての問題に対する銀の弾丸は無いという事を意味します。 ただし、APIとあなた自身のコードを混ぜることで、容易にこの状況に適用させることが可能です。

あるキーを持つハッシュを含む属性のホワイトリストが必要である状況を想像してみてください。 Strongパラメータを使用して、あるキーを持つハッシュを許可することは出来ませんが、 単純な割り当てをすることで、そのジョブを行う事が出来ます。

def product_params
  params.require(:product).permit(:name).tap do |whitelisted|
    whitelisted[:data] = params[:product][:data]
  end
end

5. セッション

Webアプリケーションはリクエスト間で持続性を保たせるために、各ユーザー毎に少量のデータを格納するセッションを持ちます。 セッションはコントローラーとビューでのみ利用することができ、 いくつかの異なるストレージメカニズムのうちの1つを使用することが出来ます。

ActionDispatch::Session::CookieStore
クライアント上に全て格納します。
ActionDispatch::Session::CacheStore
Railsキャッシュ内にデータを格納します。
ActionDispatch::Session::ActiveRecordStore
Active Recordを使用してデータベース内にデータを格納します。 (activerecord-session_storeのGemが必要です。)
ActionDispatch::Session::MemCacheStore
memcachedクラスタ内にデータを格納します。 (これまでの古い実装方法であるため、代わりにCacheStoreを使用することを検討してください。)

全てのセッション格納は、各セッション毎に一意のIDを格納するために、Cookieを使用します。 (Railsはセキュリティの観点から、セッションIDをURLで渡すことを許可しないため、Cookieを使用しなければいけません。)

ほとんどのセッション格納で、このIDはサーバ上のセッションデータを探すために使用されます。 (例: データベーステーブル内のレコード) 1つ例外があり、デフォルトであり、また推奨されているセッション格納であるCookieStoreは、 全てのセッションデータをCookie自身に格納します。 (IDは必要であれば利用することが可能) これは非常に手軽で新しいアプリケーションをセットアップする際に、 セッションを使用するために何も用意する必要が無いというメリットがあります。 Cookieデータは改竄されないように暗号的な署名をしますが、 暗号化では無いため、誰かがそのデータにアクセスして読むことは可能ですが編集することは出来ません。 (編集が行われた場合、Railsはそれを受け付けません)

CookieStoreは他のものと比べると非常に少ない4kB程度のデータを格納できますが、それで十分です。 多くの量のデータをセッションに格納することは、アプリケーションでどのセッション格納を使用していたとしても推奨されません。 特にセッション内への複雑なオブジェクト(基本的なRubyオブジェクト以外、最も一般的な例であればモデルインスタンス)の格納は、 もしかしたらサーバがリクエスト時にそれらを再構築することが出来ず、その結果エラーになるかもしれないので避けるべきです。

もし、あなたのユーザーのセッションが重要なデータでは無い、または長期間保有する必要がなければ、 (例えば、メッセージ表示に使用するflash)ActionDispatch::Session::CacheStoreの使用を検討することが出来ます。 これは、あなたのアプリケーションで設定されたキャッシュ実装を使用して、セッションを格納します。 このメリットは、既存のキャッシュ基盤をセッションの格納に使用するため、 追加の設定または管理者の許可を必要としない事です。 デメリットは、当然ですがセッション期間が短命でいずれかのタイミングで消滅してしまうことです。

セッションストレージについて更に知りたければ、Railsのセキュリティガイドを参照してください。

もし、異なるメカニズムのセッションストレージが必要であれば、 config/initializers/session_store.rbファイルで変更することが可能です。

# Use the database for sessions instead of the cookie-based default,
# which shouldn't be used to store highly confidential information
# (create the session table with "rails g active_record:session_migration")
# YourApp::Application.config.session_store :active_record_store

# 重大な機密情報を格納すべきでは無いデフォルトのCookieベースのセッションの代わりに、
# データベースを使用します。
# ("rails g active_record:session_migration"でセッションのDBテーブルを作成します。)
# YourApp::Application.config.session_store :active_record_store

Railsはセッションデータを署名する際にセッションキー(Cookieの名前)を設定します。 これらも、config/initializers/session_store.rb内で変更可能です。

# Be sure to restart your server when you modify this file.
# (このファイルを修正したら、サーバを再起動してください)
YourApp::Application.config.session_store :cookie_store, key: '_your_app_session'

また、Cookieのために:domainキーを渡して、ドメイン名を指定することも可能です。

# Be sure to restart your server when you modify this file.
# (このファイルを修正したら、サーバを再起動してください)
YourApp::Application.config.session_store :cookie_store, key: '_your_app_session', domain: ".example.com"

Railsはセッションデータの署名に使用される秘密鍵を設定(CookieStoreのための)します。 これは、config/initializers/secret_token.rb内で変更することが出来ます。

# Be sure to restart your server when you modify this file.

# Your secret key for verifying the integrity of signed cookies.
# If you change this key, all old signed cookies will become invalid!
# Make sure the secret is at least 30 characters and all random,
# no regular words or you'll be exposed to dictionary attacks.
YourApp::Application.config.secret_key_base = '49d3f3de9ed86c74b94ad6bd0...'


# このファイルを修正したら、サーバを再起動してください

# 署名したCookieの整合性を確認するための秘密鍵です。
# もし、この鍵を変更すると、全ての古いCookieの署名が無効になります!
# 秘密鍵は少なくとも30文字の、通常の単語の無い全てランダムな文字列にしてください。
# さもなくば、ディクショナリー攻撃の危険に晒されることになります。

YourApp::Application.config.secret_key_base = '49d3f3de9ed86c74b94ad6bd0...'

CookieStore使用時の秘密鍵の変更は、既存の全てのセッションを無効にします。

5.1 セッションへのアクセス

コントローラー内で、sessionインスタンスメソッドを通して、セッションにアクセスすることが出来ます。

セッションは、遅延読み込みされます。 もし、アクションのコード内でセッションにアクセスしなければ、それらが読み込まれることはありません。 そのため、何らかの処理をするためにセッションにアクセスしないのであれば、セッションを無効にする必要はありません。

セッション値はハッシュのようなキー/バリュー(key/value)のペアを使用して格納されます。

class ApplicationController < ActionController::Base

  private

  # :current_user_idキーを使用してセッション内に格納されたIDから、
  # ユーザーを見つけます。これはRailsでユーザーのログインでセッション値の設定、
  # ログアウトでそれらの削除など、ログイン周りでの処理の一般的な方法です。
  def current_user
    @_current_user ||= session[:current_user_id] &&
      User.find_by(id: session[:current_user_id])
  end
end

セッション内に何らかのデータを格納するには、ハッシュのようにキーに割り当てを行うだけです。

class LoginsController < ApplicationController
  # ログインを"Create"する(ユーザーのログイン)
  def create
    if user = User.authenticate(params[:username], params[:password])
      # セッションにユーザーIDを保存し、それが次回以降のリクエストで使用されます
      session[:current_user_id] = user.id
      redirect_to root_url
    end
  end
end

セッションから何らかのデータを削除するには、キーにnilを割り当てます。

class LoginsController < ApplicationController
  # ログインを"Delete"する(ユーザーのログアウト)
  def destroy
    # セッションからユーザーIDを削除
    @_current_user = session[:current_user_id] = nil
    redirect_to root_url
  end
end

セッション全体をリセットするには、reset_sessionを使用します。

5.2 Flash

flashは、各リクエストでクリアされる特殊なセッションの一部です。 これは、そこに格納された値は次のリクエストでしか利用することが出来ず、 エラーメッセージなどを送る場合に利用されます。

このアクセス方法はセッション、ハッシュとほとんど同じです。 (FlashHashインスタンス)

例として、ログアウト処理で使用してみましょう。 コントローラーは、次のリクエストでユーザーに対して表示することが出来るメッセージを送信することが出来ます。

class LoginsController < ApplicationController
  def destroy
    session[:current_user_id] = nil
    flash[:notice] = "ログアウトに成功しました。"
    redirect_to root_url
  end
end

リダイレクトの一部としてflashメッセージの割り当ても可能であることに注意してください。 :notice:alertまたは汎用的な目的で:flashを使用する事が出来ます。

redirect_to root_url, notice: "You have successfully logged out."
redirect_to root_url, alert: "You're stuck here!"
redirect_to root_url, flash: { referral_code: 1234 }

destroyアクションは、アプリケーションのroot_urlにリダイレクトし、 メッセージをそこに表示します。 これは、どちらかと言えば、前のアクションで設定したflashが次のアクションが何であれ、 そこに反映されるという事に注意してください。(翻訳に自信なし) アプリケーションのlayout内のflashに、通知、エラー、または警告を表示するのは常套手段になっています。

<html>
  <!-- <head/> -->
  <body>
    <% flash.each do |name, msg| -%>
      <%= content_tag :div, msg, class: name %>
    <% end -%>

    <!-- コンテンツ… -->
  </body>
</html>

この方法であれば、もしアクションに通知または警告メッセージが設定されれば、レイアウトは自動的にそれを表示します。

通知(notice)や警告(alerts)に限らず、セッションに格納出来るものは何でも渡す事が出来ます。

<% if flash[:just_signed_up] %>
  <p class="welcome">Welcome to our site!</p>
<% end %>

もし、flashの値を別のリクエストに持ち越したいのであれば、keepメソッドを使用します。

class MainController < ApplicationController
  # このアクションはroot_urlに該当し、あなたはここへの全てのリクエストが
  # UsersController#indexへリダイレクトされて欲しいと仮定します。
  # もし、アクションでflashが設定され、ここにリダイレクトされると、
  # その値はもう1つのリダイレクトが発生した際に失われますが、
  # 'keep'を使用することで継続させることが可能になります。
  def index
    # 全てのflash値が継続されます。
    flash.keep

    # キーを指定して特定の種類の値だけを継続させることも可能です。
    # flash.keep(:notice)
    redirect_to users_url
  end
end
5.2.1 flash.now

デフォルトでは、flashへの値の追加は次のリクエストで有効になりますが、 時折その値を同じリクエスト内で使用したくなるかもしれません。 例えば、createアクションがリソースの保存に失敗し、そのまま直接newテンプレートを描画する場合、 結果的にnewへはリクエストはしませんが、flashを使用したメッセージ表示は必要になるはずです。 これを行うには、通常のflashと同じようにflash.nowを使用します。

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

6. Cookie

アプリケーションはクライアント上のCookieと呼ばれる場所に少量のデータを格納することが可能で、 リクエストとセッション間の永続化を行います。 Railsではcookiesメソッドを通して、ハッシュのように(sessionとほとんど同じように)、 簡単にCookieにアクセスする事が可能です。

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)を使用する必要がある事に注意してください。

7. XMLとjsonデータの描画

ActionControllerは、非常に簡単にxmlまたはjsonデータの描画を行ってくれます。 もし、スキャフォールドを使用してコントローラーを生成したのであれば、下記のようになっているはずです。

class UsersController < ApplicationController
  def index
    @users = User.all
    respond_to do |format|
      format.html # index.html.erb
      format.xml  { render xml: @users}
      format.json { render json: @users}
    end
  end
end

上記のコードから、あなたはrender xml: @usersが使用されていて、 render xml: @users.to_xmlが使用されていない事に気づいたかもしれません。 もしオブジェクトが文字列でなければ、Railsは我々のために自動的にto_xmlを実行してくれます。

8. フィルター

フィルターはコントローラーアクションの前、後、"前後"に実行されるメソッドです。

もし、ApplicationController上でフィルターが設定されるとそのフィルターは継承され、 アプリケーション上の全ての各コントローラー上で実行されます。

"before"フィルターには、そのリクエストサイクルを中止するものがあります。 よくある"before"の1つに、そのアクションを実行させるためにユーザーのログインを必要とするものがあります。 これは次のようにしてフィルターメソッドを定義します。

class ApplicationController < ActionController::Base
  before_action :require_login

  private

  def require_login
    unless logged_in?
      flash[:error] = "ログインしてください"
      redirect_to new_login_url # リクエストサイクル中止
    end
  end
end

このメソッドは単にユーザーがログインしていなければ、 flash内にエラーメッセージを格納し、ログインフォームにリダイレクトします。 もし、"before"フィルターが描画(render)またはリダイレクト(redirect)を行うと、そのアクションは実行されません。 もし、追加のフィルターであるafterが実行される予定だったとしても、 それもキャンセルされます。

この例ではフィルターはApplicationControllerに追加されており、 そのためアプリケーション内の全てのコントローラーがそれを継承します。 これはアプリケーション内の全てにおいて、このアプリケーションを使用するのにユーザーのログインを必要とします。 ただし、当たり前のことですが全てのコントローラーまたはアクションが、これを必須とすべきではありません。 (ユーザーが最初のアクセスでログインすることは不可能です!) skip_before_action:を使用することで、特定のアクションの前に実行されるこのフィルターを無効にすることが可能です。

class LoginsController < ApplicationController
  skip_before_action :require_login, only: [:new, :create]
end

このLoginsControllernewcreateアクションは、 ユーザーのログインを強要することなく、before処理を行います。 :onlyオプションは、そのアクションだけフィルターをスキップする際に使用されます。 また、その反対の処理を行う:exceptオプションが存在ます。 これらのオプションはフィルターを追加する際にも使用することが出来るため、 最初の場所で選択されたアクションだけを実行するフィルターを追加することが可能です。

8.1 afterフィルターとaroundフィルター

"before"フィルターに加えて、アクションの後、または前後の両方でもフィルターを実行させることが可能です。

"after"フィルターは"before"フィルターと似ていますが、 アクションが実行済みであるため、クライアントに送られるであろうレスポンスデータにアクセス出来ます。 当たり前ですが、"after"フィルターはアクションの実行を停止することは出来ません。

"around"フィルターは、Rackミドルウェアが行うのと同様に、関連するアクションの実行を請け負います。

例えば、下記のようにトランザクションをその内部に適用するだけで、 承認ワークフローの変更をWebサイトの管理者が簡単にプレビューすることが出来ます。(翻訳に自信なし)

class ChangesController < ApplicationController
  around_action :wrap_in_transaction, only: :show

  private

  def wrap_in_transaction
    ActiveRecord::Base.transaction do
      begin
        yield
      ensure
        raise ActiveRecord::Rollback
      end
    end
  end
end

"around"フィルターはまた、描画(rendering)もラップすることに注意してください。 特にもし上記の例でビュー自身がデータベースの読み込む場合(例:scopeを通して)、 トランザックション内でそれを行い、データのプレビューを提供します。

アクションが実行されないケースでは、yieldを選択せずに自分でレスポンスを構築することが可能です。

8.2 その他のフィルタ使用方法

多くの場合フィルターを使用するには、プラベートメソッドを作り、*_actionにそれらを追加するのが一般的になっていますが、 他に同じことを行うための2つの方法が存在します。

1つは、*_actionに直接ブロックを使用する方法です。 ブロックはcontrollerを引数として受け取り、 前述のrequire_loginはブロックを使用して、次のように書き換える事が出来ます。

class ApplicationController < ActionController::Base
  before_action do |controller|
    redirect_to new_login_url unless controller.send(:logged_in?)
  end
end

このケースでは、logged_in?メソッドがプライベートで、フィルターはコントローラーのスコープ内で実行しないため、 フィルターがsendを使用していることに注意してください。 この独特なフィルターの実装方法は推奨されませんが、より単純なケースでは便利かもしれません。

2つ目の方法は、クラス(実際には、正しいメソッドに応答する任意のオブジェクト(翻訳に自信なし))を使用してフィルタリングを扱う方法です。 これは、より複雑で2つの異なるメソッドで読み込みと再利用可能な実装が必要な場合に便利です。 例として、再度ログインフィルターをクラスを使用して次のように書き換えることが出来ます。

class ApplicationController < ActionController::Base
  before_action LoginFilter
end

class LoginFilter
  def self.filter(controller)
    unless controller.send(:logged_in?)
      controller.flash[:error] = "You must be logged in"
      controller.redirect_to controller.new_login_url
    end
  end
end

上記は、コントローラーのスコープ内では実行されないのに、引数としてコントローラーを取得しているので、 このフィルターの例としては理想的なものではありません。 フィルタークラスは、クラスメソッドfilterを持ち、 それがbefore、afterどちらかのフィルターなのかに依って、アクションの前、または後に実行されます。 aroundフィルターとして使用されるクラスも、同じ方法で実行される同じfilterメソッドを使用することが出来ます。 そのメソッドは、アクションを実行するためにyieldしなければいけません。 あるいはまた、アクションの前後で実行されるbeforeafterメソッドの両方を持つことが可能でます。

9. リクエストフォージェリの防御

クロスサイト・リクエスト・フォージェリは、ユーザが気づかないうちに、または意図せずに、 サイトのトリックにより別のサイト上のデータの追加・修正・削除を行う種類の攻撃方法です。

この攻撃を回避するための第1段階は、全ての"破壊的な"アクション(create、update、destory)は、 GET以外のリクエストでしかアクセス出来ないようにすることです。 もし、ReSTful規約に従っているのであれば、既にこれが行われています。 ただし、悪意のあるサイトからはGET以外のリクエストを、あなたのサイトに簡単にリクエストすることが出来てしまいます。 ここでリクエスト・フォージェリ防御の出番になります。 名前の通り、リクエスト・フォージェリ攻撃を防ぎます。

これを行うために、各リクエストでサーバーだけが知ることの出来る推測不可能なトークンを追加します。 これを行うことで、正しいトークンがリクエストに含まれなければ、そのアクセスを拒否します。

もし、下記のようにしてフォームを生成したのであれば、

<%= form_for @user do |f| %>
  <%= f.text_field :username %>
  <%= f.text_field :password %>
<% end %>

トークンがどのようにhiddenフィールドに追加されたのかを確認することが出来ます。

<form accept-charset="UTF-8" action="/users/1" method="post">
<input type="hidden"
       value="67250ab105eb5ad10851c00a5621854a23af5489"
       name="authenticity_token"/>
<!-- fields -->
</form>

Railsは、このトークンをフォームヘルパーを使用して生成された各フォームに追加するので、 ほとんどの場合でトークンについて気にする必要はありません。 もし、手動でフォームを書かなければならない、または別の理由でトークンを追加する必要があるといった場合は、 form_authenticity_tokenメソッドを通して、それを利用することが出来ます。

form_authenticity_tokenは、有効な検証トークンを生成します。 カスタムされたAjax呼び出しのようなRailsが自動的に追加できないような場所で便利です。

セキュリティガイドに、この事に関するセキュリティについての説明が数多くありますので、 アプリケーションを開発する際には、こちらにも一通り目を通しておくことをお勧めします。

10. リクエスト・レスポンスのオブジェクト

各コントローラーには現在実行中のリクエストサイクル内の、 リクエストとレスポンスそれぞれのオブジェクトへの2つのアクセサメソッドが存在します。 そのrequestメソッドはAbstractRequestのインスタンスを含み、 responseメソッドはクライアントに返されるレスポンスに対応するレスポンスオブジェクトを返します。

10.1 requestオブジェクト

リクエストオブジェクトは、クライアントからのリクエストについての有用な情報を多く含みます。 利用可能な全ての一覧リストについては、APIドキュメントを参照してください。 下記は、このオブジェクトでアクセス出来るプロパティになります。

requestのプロパティ 用途
host このリクエストで使用されているホスト名です。
domain(n=2) 右(TLD)から数えて、ホスト名の最初のセグメント(n番目)
format クライアントによってリクエストされたコンテンツタイプ。
method リクエストに使用されているHTTPメソッドです。
get?、post?、patch?、put?、delete?、head? HTTPメソッドが、それぞれGET/POST/PATCH/PUT/DELETE/HEADで一致するばtrueを返します。
headers リクエストのヘッダーに関係する情報を含むハッシュを返します。
port リクエストに使用されているポート番号(integer)です。
protocol プロトコルに使用されている文字列に"://"を加えて返します。 例えば、"http://"です。
query_string URLの"?"の後のクエリー文字列の部分を返します。
remote_ip クライアントのIPアドレスです。
url リクエストで使用されているURLです。
10.1.1 path_parameters、query_parameters、request_parameters

Railsはクエリー文字列として、またはPOSTボディとして送信されたものかどうかに関わらず、 リクエストで送信された全てのパラメーターをparamsハッシュ内に集めます。 リクエストオブジェクトは、どこから来たかに応じて、それらのパラメータにアクセスする3つのアクセサーを持ちます。 query_parametersハッシュはクエリー文字列部として送信されるパラメータを含み、 request_parametersハッシュはPOSTボディ部として送信されるパラメータを含みます。 path_parametersハッシュは、ルーティングによって認識された、 現在のコントローラーとアクションへのパス部のパラメータを含みます。

10.2 responseオブジェクト

レスポンスオブジェクトは通常、直接使用されることはありませんが、アクションの実行、ユーザーに送り返すデータの描画中に構築され、 afterフィルターの後にアクセスしたい等の稀なケースで、レスポンスに直接アクセスするのに便利です。(翻訳に自信なし) これらのアクセサーメソッドの一部には、値を変更することが出来るsetterを持つものもあります。

responseのプロパティ 用途
body クライアントに送り返されるデータの文字列です。 多くの場合、これはHTMLになります。
status 成功なら200、"file not found"なら404といった、レスポンスのHTTPステータスコードです。
location 指定されていれば、ユーザーがリダイレクトされるURLになります。
content_type レスポンスのコンテンツタイプです。
charset レスポンスに使用される文字セットです。 デフォルトは"utf-8"です。
headers レスポンスに使用されるヘッダーです。
10.2.1 カスタムヘッダーの設定

もし、レスポンスにカスタムヘッダーを設定したいのであれば、response.headersでそれを行うことが出来ます。 headers属性はヘッダー名とその値とのハッシュで、Railsは自動的にそれらの一部を設定します。 もし、ヘッダーへの追加、変更を行いたい場合、response.headersに対して下記のように割り当てるだけです。

response.headers["Content-Type"] = "application/pdf"

注意: 上記の場合、content_typeセッターを直接使用する方が理にかなっています。

11. HTTP認証

Railsは2つの組み込みHTTP認証メカニズムを用意しています。

  • Basic認証
  • Digest認証

11.1 HTTP Basic認証

HTTPのBasic認証は、主要ブラウザとその他のHTTPクライアントにサポートされた認証スキームです。 例として、ユーザー名とパスワードをブラウザのBasic認証ダイアログに入力することによってのみ利用可能となる管理者セクションを考えてみます。 組み込み認証を使用すると非常に簡単で、必要とされるのはhttp_basic_authenticate_withメソッドの使用のみです。

class AdminsController < ApplicationController
  http_basic_authenticate_with name: "humbaba", password: "5baa61e4"
end

この場所であれば、AdminControllerから継承された名前空間付きのコントローラを作成することができます。 こうすることで、それらのコントロール内の全てのアクションで、HTTPのBasic認証で保護されたフィルターが実行されるようになります。

11.2 HTTP Digest 認証

HTTP Digest認証は、ネットワーク越しに暗号化されていない送信をクライアントに要求しないものとして、 Basic認証の上位に位置づけられます。 (ただし、Basic認証はHTTPS越しであれば安全です) RailsでのDigest認証の使用は非常に簡単で、必要とされるのはauthenticate_or_request_with_http_digestメソッドの使用のみです。

class AdminsController < ApplicationController
  USERS = { "lifo" => "world" }

  before_action :authenticate

  private

  def authenticate
    authenticate_or_request_with_http_digest do |username|
      USERS[username]
    end
  end
end

上記の例であれば、authenticate_or_request_with_http_digestブロックは、usernameの引数を1つだけ取得して、 パスワードを返します。 authenticate_or_request_with_http_digestからfalseまたはnilが返されると、 認証失敗となります。

12. ストリーミングとファイルダウンロード

時に、ユーザーに対してHTMLページ描画の代わりにファイルを送信したい事があるかもしれません。 Railsの全てのコントローラーは、send_datasend_fileメソッドを持ち、 これらは両方ともデータをクライアントにストリームします。 send_fileはディスク上のファイルの名前を指定して、 ファイルの内容をストリームするのに便利なメソッドです。

クライアントにデータをストリームするには、send_dataを使用します。

require "prawn"
class ClientsController < ApplicationController
  # Client情報を使用してPDFドキュメントを生成し、それを返します。
  # ユーザーはファイルのダウンロードとしてそれを取得します。
  def download_pdf
    client = Client.find(params[:id])
    send_data generate_pdf(client),
              filename: "#{client.name}.pdf",
              type: "application/pdf"
  end

  private

  def generate_pdf(client)
    Prawn::Document.new do
      text client.name, align: :center
      text "Address: #{client.address}"
      text "Email: #{client.email}"
    end.render
  end
end

上記例のdownload_pdfアクションは、実際にPDFドキュメントを生成し、 それを文字列として返すプライベートメソッドを呼び出します。 この文字列はファイルダウンロードとしてクライアントにストリームされ、 ユーザーにファイル名を入力するように求めます。 時にユーザーへのストリーミングするファイルを、ダウンロードして欲しくないケースがあるかもしれません。 例えば、HTMLページに埋め込むことが出来る画像などがあります。 ブラウザにダウンロードを意図しないファイルであることを伝えるために、 :dispositionに"inline"を指定することが出来ます。 反対にこのオプションのデフォルト値は、"attachment"になります。

12.1 ファイル送信

ディスク上に常時存在するファイルを送信したいのであれば、send_fileを使用します。

class ClientsController < ApplicationController
  # 既に作成済みでディスク内に可能されているファイルをストリーム
  def download_pdf
    client = Client.find(params[:id])
    send_file("#{Rails.root}/files/clients/#{client.id}.pdf",
              filename: "#{client.name}.pdf",
              type: "application/pdf")
  end
end

1度でのファイル全体のメモリー読み込むを避けるために、 4kBずつファイルを読み込んでストリームします。 :streamオプションを使用するか、ブロックサイズを:buffer_sizeを使用してブロックサイズを調整して、 ストリーミングをオフにすることが可能です。

もし、:typeが指定されないと、:filenameに付けられている拡張子から推測されます。 もし、拡張子のコンテンツタイプが登録されてなければ、application/octet-streamが使用されます。

クライアント(prams、cookie、他)から送られてくるデータを使用してディスク上のファイル位置を特定する場合、 こちらが意図しないファイルへのアクセスを許可してしまうかもしれないセキュリティリスクがあることに注意してください。

もしかわりにサーバ上のpublicフォルダに置いておく事が出来るのであれば、 Railsを通して静的なファイルをストリームする事は推奨されません。 更にApacheまたはその他のWebサーバーを使用して、Railsを通すことなくリクエストを保持して、 ユーザーへ直接ファイルをダンロードしてもらう方がより効率的であると言えます。

12.2 RESTfulダウンロード

send_dataでも問題はありませんが、もしあなたがRESTfulなアプリケーションを作成していて、 ファイルのダウンロードのためにアクションを分けている場合、通常こうする必要ありません。 REST上では上記の例でのPDFファイルは、クライアントの別リソースとみなすことが出来ます。 Railsは簡単でよりスマートな"RESTfulダウンロード"の方法を提供します。 下記はストリーミングを使わずにshowアクション部のPDFダウンロードを書き換えた例になります。

class ClientsController < ApplicationController
  # ユーザーはこれをHTMLまたはPDFを受け取るものとして、
  # リクエストすることが可能です。
  def show
    @client = Client.find(params[:id])

    respond_to do |format|
      format.html
      format.pdf { render pdf: generate_pdf(@client) }
    end
  end
end

上記の例が動作するためには、RailsにPDF MIMEタイプを追加する必要があります。 config/initializers/mime_types.rbファイルに下記のように追加することで、それが行う事が可能です。

Mime::Type.register "application/pdf", :pdf

設定ファイルはリクエスト毎に再読み込みされないため、 この変更を有効にするためにはサーバーを再起動する必要があります。

これでユーザーは".pdf"をURLに付け加えるだけで、リクエストでPDFファイルを取得できるようになります。

GET /clients/1.pdf

13. ログ・フィルタリング

Railsはlogフォルダ内に各環境(enviroment)毎にログファイルを保持します。 これはアプリケーションを実際に動かしてデバッグする際に非常に有用ですが、 動かしているアプリケーションで、その都度冗長な情報がログファイルに出力される事は望んでいないかもしれません。

13.1 パラメーターのフィルタリング

特定のリクエストパラメーターを、アプリケーション設定のconfig.filter_parametersに追加することで、 ログファイルからフィルタリングすることが可能です。 これらのパラメーターは、ログ内で[FILTERED]とマークされます。

config.filter_parameters << :password

13.2 リダイレクト・フィルタリング

時にログファイルからアプリケーションがリダイレクトする特定の場所において、フィルタリングをしたい事があるかもしれません。 これは、config.filter_redirect configurationを使用することで行う事が可能です。

config.filter_redirect << 's3.amazonaws.com'

この設定には、文字列、正規表現、または両方の配列を使用することが出来ます。

config.filter_redirect.concat ['s3.amazonaws.com', /private_path/]

マッチしたURLには'[FILTERED]'がマークされます。

14. Rescue

おそらく、あなたのプリケーションにはバグが含まれており、それを例外としてスローして扱う必要があるはずです。 例えば、ユーザーが参照しようとしたリソースのリンクは、データベースにはもう存在しないものだった場合、 Active RecordはActiveRecord::RecordNotFound例外をスローします。

Railsのデフォルトの例外の取り扱いでは、全ての例外を"500 Server Error"メッセージとして表示します。 ローカル環境でのリクエストであれば、トレースバックされ、 どこが間違っていてどうするべきかの追加情報を表示してくれます。 もし、ローカルでは無いRailsでのリクエストであれば、単にシンプルな"500 Server Error"メッセージをユーザーに表示し、 またルーティングエラー、レコードが見つからないような場合であれば、"404 Not Found"を表示します。 場合によっては、これらのエラーの捕捉とユーザへの表示方法をカスタマイズしたいケースがあるかもしれません。 Railsアプリケーション内では、複数階層で例外を取り扱うことが出来ます。

14.1 デフォルトの500と404テンプレート

デフォルトでは、productionのアプリケーションは404または500のエラーメッセージのどちらかを描画します。 これらのメッセージはpublicフォルダ内のそれぞれ404.html500.htmlに含まれます。 これらのファイルに情報やレイアウトを追加してカスタマイズすることが可能ですが、 これらは静的ファイルであることを忘れないでください。 RHTMLまたはlayoutsを使用することが出来ない、ただの素のHTMLです。

14.2 rescue_from

もし、エラー捕捉時にもう少し念入りに何らかの対策をしたいのであれば、 コントローラー全体とそのサブクラス内での、 特定のタイプ(または複数のタイプ)の例外を取り扱うrescue_fromを使用することが可能です。

例外が発生すると、rescue_fromディレクティブ(指定子)によってキャッチされ、 例外オブジェクトはハンドラーに渡されます。 ハンドラーはメソッドまたはProcオブジェクトとして:withオプションに渡すことが出来ます。 Procオブジェクトの代わりに、直接ブロックを使用することも可能です。

下記は、全てのActiveRecord::RecordNotFoundエラーに対してrescue_fromが割り込み、 何かを行う方法になります。

class ApplicationController < ActionController::Base
  rescue_from ActiveRecord::RecordNotFound, with: :record_not_found

  private

  def record_not_found
    render text: "404 Not Found", status: 404
  end
end

もちろん、この例は何も作り込まれておらず、デフォルトの例外の取り扱いを全く改良できていないものですが、 これらの例外を全てキャッチしてどうするかは、あなた次第です。 例えば、ユーザーがあなたのアプリケーションの特定のセクションにアクセス出来ないように、 スローされるカスタム例外クラスを作成することが可能です。

class ApplicationController < ActionController::Base
  rescue_from User::NotAuthorized, with: :user_not_authorized

  private

  def user_not_authorized
    flash[:error] = "You don't have access to this section."
    redirect_to :back
  end
end

class ClientsController < ApplicationController
  # Clientにアクセスするための正しい認証をユーザーが持っているか検証
  before_action :check_authorization

  # 全ての認証処理についてアクションが気にする必要が無いことに注意してください
  def edit
    @client = Client.find(params[:id])
  end

  private

  # もしユーザーが認証されていなければ、例外がスローされます。
  def check_authorization
    raise User::NotAuthorized unless current_user.admin?
  end
end

特定の例外は、コントローラーが初期化し、アクションが実行される前に発生するものとして、 ApplicationControllerクラスからのみrescu可能です。 詳細は、Pratik Naik氏の記事を参照してください。

15. HTTPSプロトコルの強制

セキュリティ上の理由から、特定のコントローラーへのアクセスをHTTPSプロトコルを通すように強制したいというケースがあるかもしれません。 force_sslメソッドを使用することで、コントローラーにそれを強制させることが出来ます。

class DinnerController
  force_ssl
end

フィルターと同じように、:only:exceptを指定することで、 特定のアクションのみをセキュアな接続を強制することが出来ます。

class DinnerController
  force_ssl only: :cheeseburger
  # or
  force_ssl except: :cheeseburger
end

もし、あなたが多くのコントローラーにforce_sslが追加されてる事に自身で気づいた場合、 アプリケーション全体にHTTPSを強制したいと考えるかもしれません。 そのようなケースでは、環境ファイルでconfig.force_sslを使用してみてください。

 Back to top

© 2010 - 2017 STUDIO KINGDOM