Actionコントローラーの概要
このガイドでは、コントローラーがどのように動作するか、どのようにアプリケーションのリクエストサイクルに適合するかを学びます。 このガイドを読むことで、次の事が学べるはずです。
- コントローラーを通してリクエストのフローに従う方法について
- コントローラーへのパラメータ渡しを制限する方法について
- セッションまたはCookie内のデータ格納の方法とその理由について
- リクエスト処理中のコード実行をフィルタリングしながら動作する方法について
- Actionコントローラーの組み込みHTTP認証の使用方法について
- ユーザーへのブラウザにストリームデータを直接送る方法について
- アプリケーションのログに表示されないために、機密パラメータをフィルタする方法について
- リクエスト処理中に発生するかもしれない例外の扱い方について
- 1. コントローラーは何をするか?
- 2. コントローラー命名規約
- 3. メソッドとアクション
- 4. パラメータ
- 5. セッション
- 6. Cookie
- 7. XMLとjsonデータの描画
- 8. フィルター
- 9. リクエストフォージェリの防御
- 10. リクエスト・レスポンスのオブジェクト
- 11. HTTP認証
- 12. ストリーミングとファイルダウンロード
- 13. ログ・フィルタリング
- 14. Rescue
- 15. HTTPSプロトコルの強制
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_name
とaction_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
内にあればホワイトリストをパスし、
関連する許可されたスカラ値を持つことになります。
そうでなければフィルターで除去され、配列、ハッシュ、またはその他のオブジェクトを入れることが出来ません。
許可されたスカラ値の型に出来るものには、
String
、Symbol
、NilClass
、Numeric
、
TrueClass
、FalseClass
、
Date
、Time
、DateTime
、
StringIO
、IO
、
ActionDispatch::Http::UploadedFile
、Rack::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: [] }])
これは、name
、emails
、friends
属性をホワイトリストとして宣言します。
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つを使用することが出来ます。
全てのセッション格納は、各セッション毎に一意の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
このLoginsController
のnew
とcreate
アクションは、
ユーザーのログインを強要することなく、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
しなければいけません。
あるいはまた、アクションの前後で実行されるbefore
とafter
メソッドの両方を持つことが可能でます。
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_data
とsend_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.html
と500.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
を使用してみてください。
© 2010 - 2017 STUDIO KINGDOM