本ガイドでは、コントローラの動作と、アプリケーションのリクエストサイクルにおけるコントローラの役割について解説します。
このガイドの内容:
Action Controllerは、MVCアーキテクチャの「C」に相当します。リクエストを処理するコントローラがルーターによって決定されると、コントローラはリクエストの意味を理解して適切な出力を行う役目を担います。ありがたいことに、これらの処理のほとんどはAction Controllerが行ってくれます。リクエストは、十分に吟味された規約によって可能な限りわかりやすい形で処理されます。
伝統的なRESTfulアプリケーションでは、コントローラ(C)がリクエストの受信を担当し、モデル(M)がデータの取得や保存を担当し、ビュー(V)がHTML出力を担当します。
つまり、コントローラは「モデルとビューの間を仲介する」と考えられます。コントローラがモデルのデータをビューで利用可能にすることで、データをビューで表示したり、入力されたデータでモデルを更新したりします。
Railsのコントローラは、ApplicationController
を継承したRubyのクラスであり、他のクラスと同様にメソッドが使えます。アプリケーションがブラウザからのリクエストを受け取ると、ルーターによってコントローラとアクションが確定し、Railsはそれに応じてコントローラのインスタンスを生成し、アクション名と同じ名前のメソッドを実行します。
class ClientsController < ApplicationController def new end end
上のClientsController
は、ユーザーがブラウザでアプリケーションの/clients/new
にアクセスして新しいクライアントを追加すると、ClientsController
のインスタンスが作成され、そのインスタンスのnew
メソッドが呼び出されます。
new
メソッドが存在しているが中身が空の場合、Railsはデフォルトでnew.html.erb
ビューを自動的にレンダリングします。
このnew
メソッドはインスタンスメソッドなので、ClientsController
のインスタンスで呼び出されます(つまりインスタンスを作らないと呼び出せません)。new
メソッドのように、インスタンスを作らずにClientsController.new
で呼び出せるクラスメソッドと混同しないようにしましょう。
new
クラスメソッドを実行したときの典型的な振る舞いは次の通りです。コントローラがClient
モデルのインスタンスを作成し、それをビューで@client
というインスタンス変数として利用できるようにします。
def new @client = Client.new end
ApplicationController
を継承したすべてのコントローラは、最終的にActionController::Base
を継承します。ただし、API専用アプリケーションの場合のみ、ApplicationController
はActionController::API
を継承します。
Railsのコントローラ名には、基本的に英語の「複数形」を使うのが望ましい命名です(ただし末尾の「Controller」という語は固定です)。たとえば、ClientsController
の方がClientController
より好ましく、SiteAdminsController
の方がSiteAdminController
やSitesAdminsController
よりも好ましいといった具合です。
なお、この規約は絶対ではありません(実際ApplicationController
はApplicationが単数形です)。
しかし、この規約は守っておくことをおすすめします。規約を守ることで、:controller
オプションをわざわざ書かなくても、resources
などのデフォルトのルーティングジェネレーターをそのまま利用できるようになりますし、生成される名前付きルーティングヘルパー名もアプリケーション全体で一貫するからです。
コントローラの命名規約はモデルの命名規約と異なることにご注意ください。コントローラ名は「複数形」が望ましい命名ですが、モデル名は「単数形」が望ましい命名です。
コントローラのアクションは、アクションとして呼び出し可能なpublicメソッドでなければなりません。ヘルパーメソッドのような「アクションとして外部から呼び出したくない」メソッドには、private
やprotected
を指定して公開しないようにするのが定石です。
ある種のメソッド名はAction Controllerで予約されているため、利用できません。予約済みメソッドを誤ってアクションやヘルパーメソッドとして再定義すると、SystemStackError
が発生する可能性があります。コントローラ内でRESTfulなリソースルーティングアクションだけを使うようにしていれば、メソッド名が使えなくなる心配はありません。
予約済みメソッド名をアクション名として使わざるを得ない場合は、たとえばカスタムルーティングを利用して、予約済みメソッド名を予約されていないアクションメソッド名に対応付けるという回避策が考えられます。
リクエストによって送信されたデータを受信すると、コントローラ内ではparams
ハッシュとして利用できます。パラメータのデータには以下の2種類があります:
http://example.com/accounts?filter=free
の?
以降のfilter=free
の部分)POST
パラメータ
Railsは、クエリ文字列パラメータとPOST
パラメータを区別しません。以下のように、どちらもコントローラのparams
ハッシュで同じように利用できます。
class ClientsController < ApplicationController # このアクションは、"/clients?status=activated"というURLへの # HTTP GETリクエストからクエリ文字列パラメータを受け取る def index if params[:status] == "activated" @clients = Client.activated else @clients = Client.inactivated end end # このアクションは、"/clients"というURLへのHTTP POSTリクエストに含まれる # リクエストbody内のフォームデータからパラメータを受け取る def create @client = Client.new(params[:client]) if @client.save redirect_to @client else render "new" end end end
params
ハッシュは、Rubyの単なるHash
ではなく、ActionController::Parameters
オブジェクトである点にご注意ください。このオブジェクトはRubyのHash
のように振る舞いますが、Hash
を継承していません。また、params
をフィルタリングするためのメソッドが提供され、シンボルキー:foo
と文字列キー"foo"
が同じものと見なされる点もHash
と異なります。
params
ハッシュには、一次元のキーバリューペアの他に、ネストした配列やハッシュも保存できます。値の配列をフォームから送信するには、以下のようにキー名に空の角かっこ[]
のペアを追加します。
GET /users?ids[]=1&ids[]=2&ids[]=3
[
や]
はURLで利用できない文字なので、この例の実際のURLは/users?ids%5b%5d=1&ids%5b%5d=2&ids%5b%5d=3
のようになります。これについては、ブラウザで自動的にエンコードされ、Railsがパラメータを受け取るときに自動的に復元するので、開発者が気にする必要はほとんどありません。ただし、何らかの理由でサーバーにリクエストを手動送信しなければならない場合には、このことを思い出す必要があるでしょう。
これで、受け取ったparams[:ids]
の値は["1", "2", "3"]
になりました。ここで重要なのは、パラメータの値は常に「文字列」になることです。Railsはパラメータの型推測や型変換を行いません。
params
の中にある[nil]
や[nil, nil, ...]
などの値は、セキュリティ上の理由でデフォルトでは[]
に置き換えられます。詳しくはセキュリティガイドを参照してください。
フォームからハッシュを送信するには、以下のようにキー名を角かっこ[]
の中に置きます。
<form accept-charset="UTF-8" action="/users" method="post"> <input type="text" name="user[name]" value="Acme" /> <input type="text" name="user[phone]" value="12345" /> <input type="text" name="user[address][postcode]" value="12345" /> <input type="text" name="user[address][city]" value="Carrot City" /> </form>
このフォームを送信すると、params[:user]
の値は以下のようになります。params[:user][:address]
のハッシュがネストしていることにご注目ください。
{ "name" => "Acme", "phone" => "12345", "address" => { "postcode" => "12345", "city" => "Carrot City" } }
このparams
オブジェクトの振る舞いはRubyのHash
と似ていますが、キー名にシンボルと文字列のどちらでも指定できる点がHash
と異なります。
複合キーパラメータは、1個のパラメータに複数の値を含み、値同士は区切り文字(アンダースコアなど)で区切られます。したがって、Active Recordに渡すには個別の値を抽出する必要があります。これを行うには、extract_value
メソッドを使います。
たとえば以下のコントローラがあるとします。
class BooksController < ApplicationController def show # URLパラメータから複合ID値を抽出する id = params.extract_value(:id) @book = Book.find(id) end end
ルーティングは以下のようになっているとします。
get "/books/:id", to: "books#show"
ユーザーが/books/4_2
というURLでリクエストを送信すると、コントローラは複合キー値["4", "2"]
を抽出してからBook.find
に渡します。このようにextract_value
メソッドを使うことで、区切り文字で区切られたパラメータから配列を抽出できます。
アプリケーションでAPIを公開している場合、パラメータをJSON形式で受け取ることになるでしょう。リクエストのContent-Type
ヘッダーがapplication/json
に設定されていると、Railsは自動的にパラメータをparams
ハッシュに読み込んで、通常と同じようにアクセスできるようになります。
たとえば、以下のJSONコンテンツを送信したとします。
{ "user": { "name": "acme", "address": "123 Carrot Street" } }
このとき、コントローラは以下を受け取ります。
{ "user" => { "name" => "acme", "address" => "123 Carrot Street" } }
wrap_parameters
を設定するwrap_parameters
メソッドを使うと、コントローラ名がJSONパラメータに自動で追加されます。たとえば、以下のJSONは:user
というrootキープレフィックスなしで送信できます。
{ "name": "acme", "address": "123 Carrot Street" }
上のデータをUsersController
に送信すると、以下のように:user
キー内にラップされたJSONデータも追加されます。
{ name: "acme", address: "123 Carrot Street", user: { name: "acme", address: "123 Carrot Street" } }
wrap_parameters
は、コントローラ名と同じキー内のハッシュにパラメータの複製を追加します。その結果、params
ハッシュには、パラメータの元のバージョンと「ラップされた」バージョンのパラメータの両方が存在することになります。
この機能は、パラメータを複製してから、コントローラ名に基づいて選択したキーを用いてラップします。これを制御するconfig.action_controller.wrap_parameters_by_default
設定はデフォルトでtrue
に設定されていますが、パラメータをラップしたくない場合は以下のようにfalse
に設定できます。
config.action_controller.wrap_parameters_by_default = false
キー名のカスタマイズ方法や、ラップしたいパラメータを指定する方法について詳しくは、ActionController::ParamsWrapper
APIドキュメントを参照してください。
訳注: 従来のXMLパラメータ解析のサポートは、Rails 4.0のときにactionpack-xml_parser
というgemに切り出されました。
パラメータをroutes.rb
ファイル内のルーティング宣言の一部として指定すると、そのパラメータをparams
ハッシュでも利用可能になります。たとえば、クライアントの:status
パラメータを取得するルーティングは以下のように追加できます。
get "/clients/:status", to: "clients#index", foo: "bar"
この場合、ブラウザで/clients/active
というURLを開くと、params[:status]
が"active"
(有効)に設定されます。このルーティングを使うと、クエリ文字列で渡したのと同様にparams[:foo]
にも"bar"
が設定されます。
ルーティング宣言で定義された他のパラメータ(:id
など)にも同様にアクセスできます。
上の例では、コントローラはparams[:action]
を"index"
として、params[:controller]
を"clients"
として受け取ります。params
ハッシュには常に:controller
キーと:action
キーが含まれますが、これらの値にアクセスする場合は、params[:controller]
やparams[:action]
のような方法ではなく、controller_name
メソッドやaction_name
メソッドを使うことが推奨されます。
default_url_options
メソッドコントローラで以下のようにdefault_url_options
という名前のメソッドを定義すると、url_for
ヘルパーのグローバルなデフォルトパラメータを設定できます。
class ApplicationController < ActionController::Base def default_url_options { locale: I18n.locale } end end
このメソッドで指定したデフォルトパラメータは、URLを生成する際の開始点として使われるようになります。これらのデフォルトパラメータは、url_for
に渡したオプションや、posts_path
などのパスヘルパーで上書きできます。たとえば、locale: I18n.locale
を設定すると、Railsは以下のようにすべてのURLにロケールを自動的に追加します。
posts_path # => "/posts?locale=en"
このパラメータは、必要に応じて以下のように上書きできます。
posts_path(locale: :fr) # => "/posts?locale=fr"
posts_path
ヘルパーは、内部的にはurl_for
を適切なパラメータで呼び出すためのショートハンドです。
ApplicationController
で上の例のようにdefault_url_options
を定義すると、これらのデフォルトパラメータがすべてのURL生成で使われるようになります。このdefault_url_options
メソッドは特定のコントローラで定義することも可能で、その場合は、そのコントローラ用に生成されたURLにのみ適用されます。
リクエストを受け取ったときに、生成されたURLごとにこのメソッドが常に呼び出されるとは限りません。パフォーマンス上の理由から、返されたハッシュはリクエストごとにキャッシュされます。
Action ControllerのStrongParameters
は、明示的に許可されていないパラメータをActive Modelの「マスアサインメント(mass-assignment: 一括代入)」で利用することを禁止します。したがって、開発者は、どの属性でマスアップデート(mass-update: 一括更新)を許可するかをコントローラで必ず明示的に宣言しなければなりません。strong parametersは、ユーザーがモデルの重要な属性を誤って更新してしまうことを防止するためのセキュリティ対策です。
さらに、strong parametersではパラメータを必須(つまり省略不可)として指定できます。リクエストで渡された必須パラメータが不足している場合は、400 Bad Requestを返します。
class PeopleController < ActionController::Base # 以下のコードはActiveModel::ForbiddenAttributesError例外を発生する # (明示的に許可していないパラメータを一括で渡してしまう危険な「マスアサインメント」が行われているため) def create Person.create(params[:person]) end # 以下のコードは`person_params`ヘルパーメソッドを使っているため正常に動作する。 # このヘルパーメソッドには、マスアサインメントを許可する`expect`呼び出しが含まれている。 def update person = Person.find(params[:id]) person.update!(person_params) redirect_to person end private # 許可するパラメータは、このようにprivateメソッドでカプセル化するのがよい手法である。 # このヘルパーメソッドは、createとupdateの両方で同じ許可リストを与えるのに使える。 def person_params params.expect(person: [:name, :age]) end end
expect
Rails 8から導入されたexpect
メソッドは、パラメータの必須化とパラメータの許可を同時に行うための簡潔かつ安全な方法を提供します。
id = params.expect(:id)
上のexpect
は常にスカラー値を返すようになり、配列やハッシュを返すことはありません。
expect
の別の利用例としてはフォームパラメータがあります。以下のようにexpect
を使うことで、rootキーが存在することと、属性が許可されていることを保証できます。
user_params = params.expect(user: [:username, :password]) user_params.has_key?(:username) # => true
上の例では、:user
キーが指定のキー(:username
と:password
)を持つ「ネストしたハッシュ」でない場合、expect
はエラーを発生して「400 Bad Request」レスポンスを返します。
パラメータのハッシュ全体に対して(つまりハッシュの内容を問わずに)必須化と許可を同時に行いたい場合は、expect
で以下のように空ハッシュ{}
を指定することも「一応」可能です。
params.expect(log_entry: {})
この場合、:log_entry
パラメータハッシュとそのサブハッシュはすべて許可済みとして扱われ、スカラー値が許可済みかどうかのチェックも行われなくなるため、どんなサブハッシュを渡してもすべて受け入れるようになります。
このように空ハッシュ{}
を渡してexpect
を呼び出すと、現在のモデル属性だけでなく、今後追加されるすべてモデル属性も無条件にマスアサインメントされる可能性があるため、取り扱いには細心の注意が必要です。
permit
permit
を呼び出すと、params
内の指定したキー(以下の例では:id
または:admin
)をcreateアクション
やupdate
アクションなどのマスアサインメントに含めることを許可できます。
params = ActionController::Parameters.new(id: 1, admin: "true") #=> #<ActionController::Parameters {"id"=>1, "admin"=>"true"} permitted: false> params.permit(:id) #=> #<ActionController::Parameters {"id"=>1} permitted: true> params.permit(:id, :admin) #=> #<ActionController::Parameters {"id"=>1, "admin"=>"true"} permitted: true>
上で許可した:id
キーの値は、以下の許可済みスカラー値のいずれかでなければなりません。
String
Symbol
NilClass
Numeric
TrueClass
FalseClass
Date
Time
DateTime
StringIO
IO
ActionDispatch::Http::UploadedFile
Rack::Test::UploadedFile
permit
を呼び出さなかったキーは、フィルタで除外されます(訳注: エラーは発生しません)。配列やハッシュ、およびその他のオブジェクトは、デフォルトでは挿入されません。
許可済みのスカラー値を要素に持つ配列をparams
の値に含めることを許可するには、以下のようにキーに空配列[]
を対応付けます。
params = ActionController::Parameters.new(tags: ["rails", "parameters"]) #=> #<ActionController::Parameters {"tags"=>["rails", "parameters"]} permitted: false> params.permit(tags: []) #=> #<ActionController::Parameters {"tags"=>["rails", "parameters"]} permitted: true>
ハッシュ値をparams
の値に含めることを許可するには、以下のようにキーに空ハッシュ{}
を対応付けます。
params = ActionController::Parameters.new(options: { darkmode: true }) #=> #<ActionController::Parameters {"options"=>{"darkmode"=>true}} permitted: false> params.permit(options: {}) #=> #<ActionController::Parameters {"options"=>#<ActionController::Parameters {"darkmode"=>true} permitted: true>} permitted: true>
上のpermit
呼び出しは、options
内の値が許可済みのスカラー値であることを保証し、それ以外のものをフィルタで除外します。
ハッシュパラメータや、その内部構造の有効なキーをいちいち宣言することが不可能な場合や不便な場合があるため、permit
に空ハッシュ{}
を渡せるのは確かに便利ではあります。ただし、上のようにpermit
で空ハッシュ{}
を指定すると、ユーザーがどんなデータでもパラメータとして渡せるようになってしまうことは認識しておかなければなりません。
permit!
値をチェックせずにパラメータのハッシュ全体を許可する!
付きのpermit!
メソッドも利用可能です。
params = ActionController::Parameters.new(id: 1, admin: "true") #=> #<ActionController::Parameters {"id"=>1, "admin"=>"true"} permitted: false> params.permit! #=> #<ActionController::Parameters {"id"=>1, "admin"=>"true"} permitted: true>
permit!
を使うと、現在のモデル属性だけでなく、今後追加されるすべてのモデル属性も無条件にマスアサインメントされる可能性があるため、取り扱いには細心の注意が必要です。
expect
(またはpermit
)は、以下のようにネストしたパラメータ(ネステッドパラメータ)に対しても使えます。
# 期待されるパラメータの例: params = ActionController::Parameters.new( name: "Martin", emails: ["me@example.com"], friends: [ { name: "André", family: { name: "RubyGems" }, hobbies: ["keyboards", "card games"] }, { name: "Kewe", family: { name: "Baroness" }, hobbies: ["video games"] }, ] ) # パラメータは以下のexpectによって許可済みであることが保証される: name, emails, friends = params.expect( :name, # 許可済みのスカラー値 emails: [], # 許可済みのスカラー値の配列 friends: [[ # 許可済みのParameterハッシュの配列 :name, # 許可済みのスカラー値 family: [:name], # family: { name: "許可済みのスカラー値" } hobbies: [] # 許可済みのスカラー値の配列 ]] )
この宣言は、name
属性、emails
属性、friends
属性を許可し、それぞれ以下を返すことが前提となっています。
emails
属性: 許可済みのスカラー値を要素に持つ配列を返すfriends
は特定の属性を持つリソースの配列を返す
(配列を明示的に必須にするための新しい二重配列構文[[ ]]
が使われていることに注意)
このリソースには以下の属性が必要:
name
属性: 許可済みの任意のスカラー値hobbies
属性: 許可済みのスカラー値を要素に持つ配列family
属性: name
キーと、任意の許可済みスカラー値を持つハッシュのみに制限される訳注: この特殊な二重配列構文[[:属性名]]
は、Rails 8.0に導入された新しい配列マッチング構文です(#51674)。[[:属性名]]
は、(ハッシュではなく)配列を渡さなければならないことを意味します。ただし、従来からあるpermit
では、後方互換性のため、[[:属性名]]
が指定されている場合にハッシュを渡しても許容されますが、新しいexpect
では、[[:属性名]]
に配列以外のものを渡すとエラーになります。
permit
やexpect
の使い方の例をいくつか紹介します。
例1: new
アクションで許可済み属性を使いたい場合の利用法です(new
を呼び出した時点ではrootキーが存在しないのが普通なので、そのままではrootキーに対してrequire
を呼び出せません)。
# この場合は以下のように`fetch`を使うことで、デフォルトを指定しつつ # Strong Parameters APIを利用できるようになる。 params.fetch(:blog, {}).permit(:title, :author)
例2: モデルクラスのaccepts_nested_attributes_for
メソッドによって、関連付けられているレコードの更新や破棄を行えるようになります。この例は、id
パラメータと_destroy
パラメータに基づいています。
# `:id`と`:_destroy`が許可される params.expect(author: [ :name, books_attributes: [[ :title, :id, :_destroy ]] ])
例3: integerキーを持つハッシュを異なる方法で処理し、属性を直接の子であるかのように宣言できます。accepts_nested_attributes_for
とhas_many
関連付けを組み合わせると、以下のようなパラメータを得られます。
# 以下のようなデータが許可される: # {"book" => {"title" => "Some Book", # "chapters_attributes" => { "1" => {"title" => "First Chapter"}, # "2" => {"title" => "Second Chapter"}}}} params.expect(book: [ :title, chapters_attributes: [[ :title ]] ])
例4: 製品名を表すname
パラメータと、その製品に関連付けられた任意のdata
ハッシュがあり、製品名のname
属性と、data
ハッシュ全体(空ハッシュ{}
で指定)を許可したい場合のシナリオを想定しています。
def product_params params.expect(product: [ :name, data: {} ]) end
cookieの概念は、Rails固有ではありません。cookie (HTTP cookieやWeb cookieとも呼ばれます)は、サーバーから送信されてユーザーのブラウザに保存される小さなデータです。
ブラウザ側では、cookieを保存することも、新しいcookieを作成したり、既存のcookieを変更することも、以後のリクエストでサーバーに送り返すことも可能です。Webリクエスト間のデータがcookieとして保存されることで、Webアプリケーションはユーザーの設定を記憶できるようになります。
Railsでは、cookies
メソッドを使うことで、ハッシュのようにcookieにアクセスできます。
class CommentsController < ApplicationController def new # cookieにコメント作者名が残っていたらフィールドに自動入力する @comment = Comment.new(author: cookies[:commenter_name]) end def create @comment = Comment.new(comment_params) if @comment.save if params[:remember_name] # コメント作者名をcookieに保存する cookies[:commenter_name] = @comment.author else # コメント作者名がcookieに残っていたら削除する cookies.delete(:commenter_name) end redirect_to @comment.article else render action: "new" end end end
cookieを削除するには、cookies.delete(:key)
メソッドを使う必要があります。key
にnil
値を設定してもcookieは削除されません。
cookieにスカラー値を渡した場合は、ユーザーがブラウザを閉じたときにそのcookieが削除されます。cookieを期限切れにする日時を指定したい場合は、cookieを設定するときに:expires
オプションを指定したハッシュを渡します。
たとえば、設定するCookieを1時間で失効させるには、次のようにします。
cookies[:login] = { value: "XJ-122", expires: 1.hour }
有効期限のないCookieを作成したい場合は、以下のようにcookieでpermanent
メソッドを使います。これにより、割り当てられたcookieの有効期限が20年後に設定されます。
cookies.permanent[:locale] = "fr"
cookieはクライアントのブラウザに保存されるため、クライアントによって改ざんされる可能性があり、機密データを保存するうえで安全とは言えません。Railsでは、機密データの保存用に「署名付きcookie(signed cookie)」と「暗号化cookie(encrypted cookie)」を提供しています。
詳しくはCookies
APIドキュメントを参照してください。
class CookiesController < ApplicationController def set_cookie cookies.signed[:user_id] = current_user.id cookies.encrypted[:expiration_date] = Date.tomorrow # => Thu, 20 Mar 2024 redirect_to action: "read_cookie" end def read_cookie cookies.encrypted[:expiration_date] # => "2024-03-20" end end
これらの特殊なcookieは、cookie値をシリアライザで文字列にシリアライズし、読み戻すときにRubyオブジェクトにデシリアライズします。利用するシリアライザはconfig.action_dispatch.cookies_serializer
で指定できます。新規アプリケーションのデフォルトのシリアライザは:json
です。
JSONでは、Date
、Time
、Symbol
などのRubyオブジェクトのシリアライズのサポートに制約がある点にご注意ください。これらはすべてString
にシリアライズ/デシリアライズされます。
これらのオブジェクトや、さらに複雑なオブジェクトをcookieに保存する必要がある場合は、以後のリクエストでcookieを読み取るときに手動で値を変換する必要が生じる場合があります。
cookieセッションストアを使う場合、上記はsession
やflash
ハッシュにも適用されます。
cookieはクライアント側(ブラウザ)に保存されますが、セッションデータはサーバー側(メモリ、データベース、またはキャッシュ)に保存されます。
セッションデータの有効期間は通常一時的であり(例: ブラウザを閉じるまで)、ユーザーのセッションに関連付けられます。セッションのユースケースの1つは、ユーザー認証などの機密データの保存です。
Railsアプリケーションでは、コントローラとビューでセッションを利用できます。
コントローラ内のセッションにアクセスするにはsession
インスタンスメソッドを利用できます。セッション値はハッシュに似たキーバリューペアとして保存されます。
class ApplicationController < ActionController::Base private # セッション内の`:current_user_id`キーを探索して現在の`User`を見つけるのに使う。 # これはRailsアプリケーションでユーザーログインを扱う際の定番の方法 # ログインするとセッション値が設定され、 # ログアウトするとセッション値が削除される def current_user @current_user ||= User.find_by(id: session[:current_user_id]) if session[:current_user_id] end end
セッションに何かを保存するには、ハッシュに値を追加するのと同じ要領でキーに代入できます。
以下の例では、ユーザーが認証されると、ユーザーのid
がセッションに保存されて、以後のリクエストで使われるようになります。
class SessionsController < ApplicationController def create if user = User.authenticate_by(email: params[:email], password: params[:password]) # セッションのuser idを保存し、 # 以後のリクエストで使えるようにする session[:current_user_id] = user.id redirect_to root_url end end end
セッションからデータの一部を削除するには、そのキーバリューペアを削除します。セッションからcurrent_user_id
キーを削除する方法は、ユーザーをログアウトするときに一般に使われます。
class SessionsController < ApplicationController def destroy session.delete(:current_user_id) # 現在のユーザーもクリアする @current_user = nil redirect_to root_url, status: :see_other end end
reset_session
メソッドを使うと、セッション全体をリセットできます。セッション固定攻撃(session fixation attack)を回避するため、ログイン後にはreset_session
を実行することが推奨されています。詳しくはセキュリティガイドを参照してください。
セッションは遅延読み込み(lazy load)されるため、アクションのコードでセッションにアクセスしない限り、セッションは読み込まれません。したがって、セッションを常に明示的に無効にする必要はなく、アクセスしないようにすれば十分です。
Flash
は、コントローラのアクション同士の間で一時的なデータを渡す方法を提供します。flashに配置したものはすべて、次のアクションで利用可能になり、その後クリアされます。
flashは、ユーザーにメッセージを表示するアクションにリダイレクトする前に、コントローラのアクションでメッセージ(通知やアラートなど)を設定するときによく使われます。
flashにアクセスするにはflash
メソッドを使います。flashはセッションと同様にキーバリューペアとして保存されます。
たとえば、コントローラでユーザーをログアウトするアクションでは、次回のリクエストでコントローラがユーザーに表示できるflashメッセージを以下のように設定できます。
class SessionsController < ApplicationController def destroy session.delete(:current_user_id) flash[:notice] = "ログアウトしました" redirect_to root_url, status: :see_other end end
ユーザーがアプリケーションで何らかの対話的操作を実行した後にメッセージで結果を表示することは、アクションの成功(もしくはエラーの発生)をユーザーにフィードバックする良い方法です。
flashでは、通常の:notice
(通知)メッセージの他に、:alert
(アラート)メッセージも表示できます。これらのflashメッセージには、意味を表す色をCSSで設定するのが普通です(例: 通知は緑、アラートはオレンジや赤)。
以下のようにredirect_to
のパラメータにflashメッセージを追加することで、リダイレクト時にflashメッセージを表示することも可能です。
redirect_to root_url, notice: "ログアウトしました" redirect_to root_url, alert: "問題が発生しました"
flashメッセージの種別は、notice
やalert
だけではありません。:flash
引数にキーを割り当てることで、flashに任意のキーを設定できます。
たとえば、:just_signed_up
を割り当てるには以下のようにします。
redirect_to root_url, flash: { just_signed_up: true }
これでビューで以下の表示用のコードを書けるようになります。
<% if flash[:just_signed_up] %> <p class="welcome">Welcome to our site!</p> <% end %>
上記のログアウトの例では、destroy
アクションを実行するとアプリケーションのroot_url
にリダイレクトし、そこでflashメッセージを表示できます。ただし、このメッセージが自動的に表示されるわけではありません。直前のアクションでflashに設定した内容がどう扱われるかは、次に実行されるアクションで決定されます。
直前のアクションでflashメッセージが設定された場合は、flashメッセージをユーザーに表示するのがよいでしょう。flashメッセージを表示する以下のようなHTMLをアプリケーションのデフォルトレイアウトに追加しておけば、flashメッセージが常に表示されるようになります。
以下はapp/views/layouts/application.html.erb
にflashメッセージの表示コードを追加する例です。
<html> <!-- <head/> --> <body> <% flash.each do |name, msg| -%> <%= content_tag :div, msg, class: name %> <% end -%> <!-- (他にもコンテンツがあるとする) --> <%= yield %> </body> </html>
上のname
は、notice
やalert
などのflashメッセージの種別を表します。この情報は通常、flashメッセージをどのようなスタイルでユーザーに表示するかを指定するのに使われます。
レイアウトファイルでnotice
とalert
だけを表示するように制限したい場合は、name
でフィルタリングする方法が使えます。フィルタを行わない場合は、flash
で設定されたすべてのキーが表示されます。
flashメッセージの読み取りと表示のコードをレイアウトファイルに含めておけば、flashを読み取るロジックを個別のビューに含めなくても、アプリケーション全体で自動的に表示されるようになります。
flash.keep
とflash.now
flash.keep
は、flashの値を以後のリクエストにも引き継ぎたいときに使えます。このメソッドは、リダイレクトが複数回行われる場合に便利です。
たとえば、以下のコントローラのindex
アクションがroot_url
に対応していて、ここでのリクエストをすべてUsersController#index
にリダイレクトするとします。
アクションがflashを設定してからMainController#index
にリダイレクトすると、flash.keep
メソッドで別のリクエスト用の値を保持しておかない限り、別のリダイレクトが発生したときにflashの値は失われます。
class MainController < ApplicationController def index # すべてのflash値を保持する flash.keep # 以下のようにキーを指定すれば、特定の値だけを保持することも可能 # flash.keep(:notice) redirect_to users_url end end
flash.now
は、同じリクエストでflash値を利用可能にするときに使います。
flashに値を追加すると、デフォルトでは(現在のリクエストではなく)次回のリクエストでflashの値を利用可能になります。
たとえば、create
アクションでリソースの保存に失敗し、new
テンプレートを直接レンダリングした場合は、新しいリクエストが発生しないため、flashメッセージは表示されません。
しかし、そのような場合でもflashメッセージを表示したいことがあります。これを行うには、通常のflash
と同じようにflash.now
を使うと、現在のリクエストでflashメッセージが表示されるようになります。
class ClientsController < ApplicationController def create @client = Client.new(client_params) if @client.save # ... else flash.now[:error] = "クライアントを保存できませんでした" render action: "new" end end end
すべてのセッションには、セッションオブジェクトを表す一意のIDが存在し、これらのセッションIDはcookieに保存されます。実際のセッションオブジェクトは、次のいずれかの保存メカニズムを利用します。
ActionDispatch::Session::CookieStore
: すべてをクライアント側に保存するActionDispatch::Session::CacheStore
: データをRailsのキャッシュに保存するActionDispatch::Session::ActiveRecordStore
: Active Recordデータベースに保存する(activerecord-session_store
gemが必要)ほとんどのセッションストアでは、サーバー上のセッションデータ(データベーステーブルなど)を検索するときに、cookie内にある一意のセッションidを使います。セッションIDをURLとして渡す方法は安全性が低いため、Railsでは利用できません。
CookieStore
CookieStore
はデフォルトで推奨されるセッションストアで、すべてのセッションデータをcookie自体に保存します(セッションIDも必要に応じて引き続き利用可能です)。CookieStore
は軽量で、新規Railsアプリケーションでは設定なしで利用できます。
CookieStore
には4KBのデータを保存できます。他のセッションストアに比べるとずっと小容量ですが、通常はこれで十分です。
利用するセッションストアの種類にかかわらず、セッションに大量のデータを保存することは推奨されていません。特に、モデルインスタンスのような複雑なオブジェクトをセッションに保存することは避けてください。
CacheStore
CacheStore
は、セッションに重要なデータを保存しない場合や、長期間の保存が不要な場合(例: メッセージングにflashだけを使う場合)に利用できます。これにより、アプリケーション用に構成したキャッシュ実装を利用してセッションが保存されるようになります。
CacheStore
のメリットは、追加のセットアップや管理を必要とせずに、既存のキャッシュインフラストラクチャを利用してセッションを保存できることです。
デメリットは、セッションの保存が一時的なものに限られ、いつでも消えてしまう可能性があることです。
セッションストレージについて詳しくはセキュリティガイドを参照してください。
Railsでは、セッションストレージに関連するいくつかの設定オプションを利用できます。 利用するストレージの種類は、イニシャライザで以下のように設定できます。
Rails.application.config.session_store :cache_store
Railsは、セッションデータに署名するときにセッションキー(cookieの名前)を設定します。この動作もイニシャライザで変更できます。
Rails.application.config.session_store :cookie_store, key: "_your_app_session"
イニシャライザファイルの変更を反映するには、サーバーを再起動する必要があります。
以下のように:domain
キーを渡して、cookieを使うドメイン名を指定することも可能です。
Rails.application.config.session_store :cookie_store, key: "_your_app_session", domain: ".example.com"
詳しくはRails設定ガイドのconfig.session_store
を参照してください。
Railsは、config/credentials.yml.enc
のセッションデータの署名に用いる秘密鍵をCookieStore
に設定します。この秘密鍵はbin/rails credentials:edit
コマンドで変更できます。
# aws: # access_key_id: 123 # secret_access_key: 345 # Used as the base secret for all MessageVerifiers in Rails, including the one protecting cookies. secret_key_base: 492f...
CookieStore
を利用中にsecret_key_base
を変更すると、既存のセッションがすべて無効になります。既存のセッションをローテーションするには、Cookieローテーターの設定が必要です。
訳注: Rails 7.2で従来のフィルタ(filter)という用語がアクションコールバック(action callback)に置き換えられ、その後さらにRails 8でコントローラコールバック(あるいは単にコールバック)に置き換えられました。
コントローラコールバック(controller callback)は、コントローラのアクションが実行される「直前(before)」、「直後(after)」、あるいは「直前と直後(around)」に実行されるメソッドです。
コントローラコールバックのメソッドは、特定のコントローラで定義することもApplicationController
で定義することも可能です。すべてのコントローラはApplicationController
を継承するので、ここで定義されたコールバックはアプリケーション内のすべてのコントローラで実行されます。
before_action
before_action
に登録したコールバックメソッドは、コントローラのアクションの直前に実行されます。リクエストサイクルを止めてしまう可能性があるのでご注意ください。before_action
の一般的なユースケースは、ユーザーがログイン済みであることを確認することです。
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_action
コールバックによってビューのレンダリングや前述の例のようなリダイレクトが行われると、コントローラのこのアクションは実行されなくなる点にご注意ください。コールバックの実行後に実行されるようスケジュールされた追加のコントローラコールバックが存在する場合は、これらもキャンセルされ、実行されなくなります。
上の例では、before_action
をApplicationController
で定義しているため、アプリケーション内のすべてのコントローラに継承されます。つまり、アプリケーション内のあらゆるリクエストでユーザーのログインが必須になります。
これは他の部分では問題ありませんが、「ログイン」ページだけは別です。「ログイン」操作はユーザーがログインしていない状態でも成功する必要があり、そうしておかないとユーザーがログインできなくなります。
skip_before_action
を使えば、特定のコントローラアクションでのみ指定のbefore_action
をスキップできます。
class LoginsController < ApplicationController skip_before_action :require_login, only: [:new, :create] end
上のようにすることで、ユーザーがログインしていなくてもLoginsController
のnew
アクションとcreate
アクションが成功するようになります。
特定のアクションでのみコールバックをスキップしたい場合には、:only
オプションでアクションを指定します。逆に特定のアクションのみコールバックをスキップしたくない場合は、:except
オプションでアクションを指定します。
これらのオプションはコールバックの追加時にも使えるので、最初の場所で選択したアクションに対してだけ実行されるコールバックを追加することも可能です。
同じコールバックを異なるオプションで複数回呼び出すと、最後に呼び出されたアクションコールバックの定義によって、それまでのコールバックの定義は上書きされます。
after_action
コールバックとaround_action
コールバックコントローラアクションが実行される「直後」に実行するアクションコールバックは、after_action
で定義できます。
コントローラアクションが実行される「直前」と「直後」に実行するアクションコールバックは、around_action
で定義できます。
after_action
コールバックはbefore_action
コールバックに似ていますが、コントローラアクションがすでに実行済みのため、クライアントに送信されるレスポンスデータにアクセスできる点が異なります。
after_action
コールバックは、アクションが成功した場合にのみ実行されます。リクエストサイクルの途中で例外が発生した場合は実行されません。
around_action
コールバックは、コントローラアクションの直前と直後にコードを実行する場合に便利で、アクションの実行に影響する機能をカプセル化できます。これらは、関連するアクションをyield
で実行させる役割を担います。
たとえば特定のアクションのパフォーマンスを監視したいとします。以下のようにaround_action
を使うことで、各アクションが完了するまでにかかる時間を測定した情報をログに出力できます。
class ApplicationController < ActionController::Base around_action :measure_execution_time private def measure_execution_time start_time = Time.now yield # ここでアクションが実行される end_time = Time.now duration = end_time - start_time Rails.logger.info "Action #{action_name} from controller #{controller_name} took #{duration.round(2)} seconds to execute." end end
アクションコールバックには、上の例に示したようにcontroller_name
と action_name
が利用可能なパラメータとして渡されます。
around_action
コールバックはレンダリングもラップします。上の例では、ビューのレンダリングはduration
の値に含まれます。
around_action
のyield
以降のコードは、関連付けられたアクションで例外が発生すれば、コールバックにensure
ブロックが存在する場合でも実行されます(この振る舞いは、アクションで例外が発生するとafter_action
コードがキャンセルされるafter_action
コールバックとは異なります)。
before_action
、after_action
、around_action
の他にも、あまり一般的ではないコールバック登録方法が2つあります。
1つ目の方法は、*_action
メソッドに直接ブロックを渡すことです。このブロックはコントローラを引数として受け取ります。
たとえば、前述のrequire_login
アクションコールバックを書き換えてブロックを使うようにすると、以下のようになります。
class ApplicationController < ActionController::Base before_action do |controller| unless controller.send(:logged_in?) flash[:error] = "このセクションにアクセスするにはログインが必要です" redirect_to new_login_url end end end
このとき、コールバック内でsend
メソッドを使っていることにご注意ください。
その理由は、logged_in?
はprivateメソッドであり、そのままではコールバックがコントローラのスコープで実行されないためです(訳注: send
メソッドを使うとprivateメソッドを呼び出せます)。
この方法は、特定のコールバックを実装する方法としては推奨されませんが、もっとシンプルな場合には役に立つことがあるかもしれません。
特にaround_action
の場合、以下のコードのtime(&action)
はコントローラアクションをブロックとしてtime
メソッドに渡します。
around_action { |_controller, action| time(&action) }
2つ目の方法は、コールバックアクションにクラス(または期待されるメソッドに応答する任意のオブジェクト)を指定することです。 これは、より複雑なコールバックコードをシンプルに書くときに便利です。
たとえば、around_action
コールバックを以下のように書き換えることで、渡したクラスを使って実行時間を測定できます。
class ApplicationController < ActionController::Base around_action ActionDurationCallback end class ActionDurationCallback def self.around(controller, action) start_time = Time.now yield # ここでアクションが実行される end_time = Time.now duration = end_time - start_time Rails.logger.info "Action #{action} from controller #{controller} took #{duration.round(2)} seconds to execute." end end
上の例では、ActionDurationCallback
クラスのメソッドはコントローラのスコープ内で実行されませんが、controller
とaction
を引数として受け取る点にご注目ください。
一般に、*_action
コールバックに渡すクラスは、そのコールバックと同じ名前のメソッドを実装する必要があります。つまり、たとえばbefore_action
コールバックに渡すクラスはbefore
メソッドを実装する必要があります。
また、around
メソッドには、アクションを実行するためのyield
が必要です。
request
オブジェクトとresponse
オブジェクトすべてのコントローラにはrequest
メソッドとresponse
メソッドが必ず存在しているので、これらを使って現在のリクエストサイクルに関連付けられたリクエストオブジェクトとレスポンスオブジェクトにアクセスできます。
request
メソッドは、ActionDispatch::Request
のインスタンスを返します。response
メソッドは、ActionDispatch::Response
のインスタンスを返します。
これは、クライアント(ブラウザ)に返す内容を表すオブジェクトです(コントローラアクションのrender
やredirect
など)。request
オブジェクトresponse
オブジェクトには、クライアントから受信したリクエストに関する有用な情報が多数含まれています。本セクションでは、request
オブジェクトの一部のプロパティの目的について説明します。
リクエストオブジェクトで利用可能なメソッドの完全なリストについては、Rails APIドキュメントのActionDispatch::Request
やRack gemのドキュメントを参照してください。
request のプロパティ |
目的 |
---|---|
host |
リクエストで使われるホスト名 |
domain(n=2) |
ホスト名の右(TLD:トップレベルドメイン)から数えてn 番目のセグメント |
format |
クライアントからリクエストされたContent-Type ヘッダー |
method |
リクエストで使われるHTTPメソッド |
get? 、post? 、patch? 、put? 、delete? 、head?
|
HTTPメソッドがGET/POST/PATCH/PUT/DELETE/HEADのいずれかの場合にtrueを返す |
headers |
リクエストに関連付けられたヘッダーを含むハッシュを返す |
port |
リクエストで使われるポート番号(整数) |
protocol |
プロトコル名に"://"を加えたものを返す("http://"など) |
query_string |
URLの一部で使われるクエリ文字("?"より後の部分) |
remote_ip |
クライアントのIPアドレス |
url |
リクエストで使われるURL全体 |
query_parameters
、request_parameters
、path_parameters
Railsのparams
には、クエリ文字列パラメータとしてURLに設定されたデータや、POST
リクエストのbodyとして送信されたデータなど、特定のリクエストに関するすべてのパラメータが集まっています。
request
オブジェクトでは、さまざまなパラメータにアクセスできる以下の3つのメソッドを利用できます。
query_parameters
: クエリ文字列の一部として送信されたパラメータが含まれます。request_parameters
: POST
のbodyの一部として送信されたパラメータが含まれます。path_parameters
: ルーターによって特定のコントローラとアクションへのパスの一部であると解析されたパラメータが含まれます。response
オブジェクトresponse
オブジェクトは、アクションの実行中に、クライアント(ブラウザ)に送り返すデータをレンダリングすることでビルドされます。
通常はresponse
オブジェクトを直接使うことはありませんが、after_action
コールバックなどでは、レスポンスに直接アクセスすると便利な場合があります。
response
オブジェクトのユースケースの1つは、Content-Type
ヘッダーを設定することです。
response.content_type = "application/pdf"
response
オブジェクトの別のユースケースとして、カスタムヘッダーを設定するのにも使われます。
response.headers["X-Custom-Header"] = "some value"
headers
属性は、ヘッダー名をヘッダー値に対応付けるハッシュです。Railsは、一部のヘッダーについては自動的に設定しますが、ヘッダーの更新やカスタムヘッダーの追加が必要な場合は、上の例のようにresponse.headers
を利用できます。
headers
メソッドには、コントローラから直接アクセスすることも可能です。
response
オブジェクトに含まれるプロパティの一部を以下に示します。
response のプロパティ |
目的 |
---|---|
body |
クライアントに送り返されるデータの文字列(HTMLで最もよく使われる) |
status |
レスポンスのステータスコード(200 OK、404 file not foundなど) |
location |
リダイレクト先URL(存在する場合) |
content_type |
レスポンスのContent-Typeヘッダー |
charset |
レスポンスで使われる文字セット(デフォルトは"utf-8") |
headers |
レスポンスで使われるヘッダー |
リクエストオブジェクトで利用可能なメソッドの完全なリストについては、Rails APIドキュメントのActionDispatch::Response
やRack gemのドキュメントを参照してください。
Railsガイドは GitHub の yasslab/railsguides.jp で管理・公開されております。本ガイドを読んで気になる文章や間違ったコードを見かけたら、気軽に Pull Request を出して頂けると嬉しいです。Pull Request の送り方については GitHub の README をご参照ください。
原著における間違いを見つけたら『Rails のドキュメントに貢献する』を参考にしながらぜひ Rails コミュニティに貢献してみてください 🛠💨✨
本ガイドの品質向上に向けて、皆さまのご協力が得られれば嬉しいです。
Railsガイド運営チーム (@RailsGuidesJP)
Railsガイドは下記の協賛企業から継続的な支援を受けています。支援・協賛にご興味あれば協賛プランからお問い合わせいただけると嬉しいです。