Last-modified: 2010-09-23 (木) 13:59:42 (1d)
Action Controller Overview †
原文 †http://guides.rubyonrails.org/action_controller_overview.html Action Controller 概要(Action Controller Overview) †このガイドではどのようにコントローラは動いているのか、どのようにアプリケーション内でリクエストサイクルに合わせているのかを学ぶことができます。
1 コントローラは何をしますか?(What Does a Controller Do?) †Action Controller は MVC の中の C です。 ほとんどの従来の RESTful なアプリケーションにとって、コントローラはリクエストを受け取り(これは開発者には見えない) コントローラは、このようにモデルとビューの間の中間的なものとして考えられています。 ルーティングの過程のさらに詳しいことは Rails Routing from the Outside In を見てください。 2 メソッドとアクション(Methods and Actions) †コントローラは ApplicationController を継承した Ruby クラスであり、他のクラスと同様にメソッドを持っています。 class ClientsController < ApplicationController def new end end 例として、もしユーザーが、新しい顧客を追加しようとして、あなたのアプリケーション上の /clients/new に行く場合、 def new @client = Client.new end Layouts & rendering guide はこれをさらに詳しく説明しています。 ApplicationController は ActionController::Base を継承しており、これはたくさんの役に立つメソッドを定義しています。 パブリックなメソッドだけを、アクションとして呼び出すことができます。 3 パラメーター(Parameters) †コントローラのアクション内で、ユーザーによって送られたデータや他のパラメータにアクセスしたいでしょう。 class ClientsController < ActionController::Base # このアクションは HTTP GET リクエストにより実行されるので、クエリ文字列を使用します。 # しかし、パラメータがどの方法でアクセスされようと、いかなる違いもありません。 # このアクションの URL は、認証された顧客をリスティングするために # /clients?status=activated のようになるでしょう。 def index if params[:status] == "activated" @clients = Client.activated else @clients = Client.unactivated end end # このアクションは POST パラメータを使用します。 # ユーザーが HTML フォームから送信したものが大半です。 # この RESTful な URL は "/clients" になるでしょう。 # そして、データはリクエスト本文の一部となって送られるでしょう。 def create @client = Client.new(params[:client]) if @client.save redirect_to @client else # この行は、 "create" ビューをレンダリングするという、デフォルトのレンダリングの # 振る舞いをオーバーライドしています。 render :action => "new" end end end 3.1 ハッシュ、配列パラメータ(Hash and Array Parameters) †この params ハッシュは、一次元のキーとバリューに制限されていません。 GET /clients?ids[]=1&ids[]=2&ids[]=3 この例の実際の URL は "/clients?ids%5b%5d=1&ids%5b%5d=2&ids%5b%5b=3" としてエンコードされ、 "[" , "]" は URL に認められません。 いま、 params[:ids] の値は ["1", "2", "3"] です。 ハッシュを送信するには、 [] の中にキー名を含ませます。 <form 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 ハッシュは実際は Active Support の HashWithIndifferentAccess のインスタンスです。 3.2 ルーティングパラメータ(Routing Parameters) †params ハッシュは常に :controller と :action キーを格納していますが、 map.connect "/clients/:status", :controller => "clients", :action => "index", :foo => "bar" この場合、ユーザーが /clients/active の URL を開いたとき、 params[:status] は "active" にセットされます。 3.3 default_url_options(default_url_options) †URL を生成するときに使われるであろうグローバルなデフォルトパラメータを、 default_url_options でセットすることができます。 class ApplicationController < ActionController::Base # オプションのパラメータは 'url_for' に渡されるハッシュです。 def default_url_options(options) {:locale => I18n.locale} end end これらのオプションは URL を生成するときの出発点として使われるでしょう、 url_for によってオーバーライドされることが可能です。 4 セッション(Session) †アプリケーションはそれぞれのユーザーのためにセッションを持っていて、少しのデータを保存することができるので、リクエスト間で永続化するでしょう。
全てのセッションストアは、それぞれのセッションのためにユニークな ID を保存するのに cookie を使います。 ほとんどのストアでは、この ID はサーバ上でセッションデータを調べるために使われます。(例:データベーステーブル内) クッキーストアはおよそ 4kb のデータを保存できます。これは他よりとても少ないが、普通これで十分です。 セッションの保存について詳しいことは Security Guide を読んでください。 もし違うセッションの保存のメカニズムについて必要なら、config/initializers/session_store.rb 内で変更することができます: # クッキーベースのデフォルトの代わりにセッションにデータベースを使うなら、 # 機密性の高い情報を格納するために使用すべきではありません。 # ( "rake db:sessions:create" でセッションテーブルを作成してください。) # YourApp::Application.config.session_store :active_record_store Rails はセッションデータを署名するとき、セッションキー(クッキーの名前)を設定します。 # このファイルを変更した時、必ずサーバーを再起動してください。 YourApp::Application.config.session_store :cookie_store, :key => '_your_app_session' また、 :domain キーを渡し、クッキーのためにドメイン名を指定することも出来ます。 # このファイルを変更した時、必ずサーバーを再起動してください。 YourApp::Application.config.session_store :cookie_store, :key => '_your_app_session', :domain => ".example.com" Rails は(クッキーストアのために) 、セッションデータを署名するために使う秘密鍵を設定します。 # このファイルを変更した時、必ずサーバーを再起動してください。 # 署名されたクッキーの整合性を検証するための秘密鍵。 # この鍵を変更すると、全ての古いセッションは無効となるでしょう! # :secretは少なくとも30文字でかつ全てランダムで、基本的な単語ではない言葉にしないと、 # 辞書攻撃に晒されるでしょう。 YourApp::Application.config.secret_token = '49d3f3de9ed86c74b94ad6bd0...' CookieStore を使っているときに秘密鍵を変えることは、すべての存在するセッションを無効にするでしょう。 4.1 セッションへのアクセス(Accessing the Session) †コントローラ内では、session インスタンスメソッドを通してセッションにアクセスできます。 セッションは Lazy Loading されます。 セッション値は、ハッシュのようにキー/値のペアーを使って保存されます。 class ApplicationController < ActionController::Base private # セッションの :current_user_id キーに保存された ID によってユーザーを検索して下さい。 # Rails アプリでユーザー認証を制御する一般的な方法です。 # ログインでセッション値を設定し、ログアウトで削除します。 def current_user @_current_user ||= session[:current_user_id] && User.find(session[:current_user_id]) end end セッションに何かを保存するのは、ハッシュのようにキーを指定するだけです: class LoginsController < ApplicationController # ログインの "作成" 、別名 "ユーザーのログイン" def create if user = User.authenticate(params[:username], params[:password]) # ユーザー ID をセッションに保存しましょう、 # それは後続のリクエストで使用できます。 session[:current_user_id] = user.id redirect_to root_url end end end セッションから何かを除くには、キーに null を指定します: class LoginsController < ApplicationController # ログインの "削除" 、別名 "ユーザーのログアウト" def destroy # セッションからユーザー"ID"を削除しましょう。 session[:current_user_id] = nil redirect_to root_url end end 全てのセッションをリセットするには、 reset_session を使います。 4.2 フラッシュ(The Flash) †フラッシュは、それぞれのリクエストとともにクリアされるセッションの特別な部分です。 class LoginsController < ApplicationController def destroy session[:current_user_id] = nil flash[:notice] = "You have successfully logged out" redirect_to root_url end end この destroy アクションはアプリケーションの root_url にリダイレクトし、そこにメッセージが表示されるでしょう。 <html> <!-- <head/> --> <body> <% if flash[:notice] -%> <p class="notice"><%= flash[:notice] %></p> <% end -%> <% if flash[:error] -%> <p class="error"><%= flash[:error] %></p> <% end -%> <!-- more content --> </body> </html> この方法では、アクションがエラーまたは通知メッセージを設定した場合、レイアウトが自動的に表示されるでしょう。 フラッシュの値を他のリクエストに持ち越したいなら、 keep メソッドを使って下さい。 class MainController < ApplicationController # 例えばこのアクションは root_url に対応していますが、 # 全てのリクエストを UsersController#index にリダイレクトしたいとしましょう。 # アクションがフラッシュを設定し、そこにリダイレクトしたなら、値は通常、他のリダイレクトが起こった時失われますが、 # keep を使えば、他のリクエストのため保持し続けることができます。 def index # 全てのフラッシュ値を保持し続けさせましょう。 flash.keep # 色んな種類の値を保持し続けるためにキーを使うこともできます。 # flash.keep(:notice) redirect_to users_url end end 4.2.1 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 5 クッキー(Cookies) †アプリケーションはクライアントに少量のデータを保存できます。 class CommentsController < ApplicationController def new # クッキーに保存されていた場合、コメント者の名前をオートフィルします。 @comment = Comment.new(:name => 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.name else # コメント者の名前のクッキーがあれば削除します。 cookies.delete(:commenter_name) end redirect_to @comment.article else render :action => "new" end end end セッション値のキーを nil に設定する時は注意して下さい、クッキー値を削除するためには cookies.delete(:key) を使うべきです。 6 xml と json データのレンダリング(Rendering xml and json data) †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} end end end 上記の場合のコードは、 render :xml => @users で、 render :xml => @users.to_xml ではないことに注意して下さい。 7 フィルタ(Filters) †フィルタは、コントローラのアクションの前、後、 "前後" に実行されるメソッドです。 フィルタは、継承されるので、 ApplicationController 上でフィルタを設定した場合、 before フィルタは、リクエストのサイクルを停止する場合があります。 class ApplicationController < ActionController::Base before_filter :require_login private def require_login unless logged_in? flash[:error] = "You must be logged in to access this section" redirect_to new_login_url # リクエストのサイクルを停止します end end # logged_in? メソッドは、ユーザーがログインしていれば true 、そうでなければ false を単に返すだけです。 # 以前作成した current_user メソッドを !! 演算子を使って、ブーリアン化するものです。 # これは Ruby で一般的ではなく、本当に true か false に変換したい場合以外は推奨されないことに注意して下さい。 def logged_in? !!current_user end end ユーザーがログインしていない場合、そのメソッドは単にフラッシュ内にエラーメッセージを格納し、ログインフォームにリダイレクトします。 この例ではフィルタは ApplicationController に追加されているので、アプリケーション内のすべてのコントローラはそれを継承します。 class LoginsController < Application skip_before_filter :require_login, :only => [:new, :create] end 今では、 LoginController の new と create アクションは、ユーザーにログインを要求することなく、動作するでしょう。 7.1 after フィルタと around フィルタ(After Filters and Around Filters) †before フィルタに加えて、アクションの後や、前後にフィルタを実行することができます。 around フィルタはアクションを実行するための責任がありますが、 # Railsのフィルタに関するAPI文書から例を取りました: # http://api.rubyonrails.org/classes/ActionController/Filters/ClassMethods.html class ApplicationController < Application around_filter :catch_exceptions private def catch_exceptions yield rescue => exception logger.debug "Caught exception! #{exception}" raise end end 7.2 フィルタを使う他の方法(Other Ways to Use Filters) †フィルタを使う最も一般的な方法は、 private メソッドを作り、それらを追加する為に *_filter を使うようにすることで、 1番目は、 *_filter メソッドと共に直接ブロックを使う方法です。 class ApplicationController < ActionController::Base before_filter do |controller| redirect_to new_login_url unless controller.send(:logged_in?) end end この場合のフィルタが send を使用していることに注意してください。
なぜなら、 logged_in? メソッドは private で、フィルタがこのコントローラーのスコープでは動作しないからです。 2番目の方法は、フィルタリングを制御する為にクラスを使用すること(実際には、適切なメソッドに対応するオブジェクトが使用されるでしょう)です。 class ApplicationController < ActionController::Base before_filter 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 繰り返しますが、これはこのフィルタのための理想的な例ではありません。 Rails API 文書には、フィルタに関する他の情報も載っています。 8 ベリファイケーション(検証)(Verification) †ベリファイケーションは、コントローラーやアクションを実行するために特定の条件が満たされていることを確認します。 ログインするために、ユーザー名とパスワードを入力し、それをベリファイケーションを使用して確認する例です: class LoginsController < ApplicationController verify :params => [:username, :password], :render => {:action => "new"}, :add_flash => { :error => "Username and password required to log in" } def create @user = User.authenticate(params[:username], params[:password]) if @user flash[:notice] = "You're logged in" redirect_to root_url else render :action => "new" end end end 今では、 create アクションは "username" と "password" パラメーターが存在しなければ実行されません、 class LoginsController < ApplicationController verify :params => [:username, :password], :render => {:action => "new"}, :add_flash => { :error => "Username and password required to log in" }, :only => :create # "create" アクションでだけ動作します end 9 リクエストの偽造防止(Request Forgery Protection) †クロスサイトリクエスト偽造は、あるサイトがユーザーを騙し、他サイト上のリクエストを作り、 これを防ぐ最初のステップは、全ての "破壊的な" アクション( create 、 update 、 destroy )は GET リクエストではアクセスさせないことです。 これを行う方法は、サーバーしか知らない、推測できないトークンを全てのリクエストに追加することです。 このようなフォームを生成する場合、 <% form_for @user do |f| -%> <%= f.text_field :username %> <%= f.text_field :password -%> <% end -%> 隠しフィールドとして、どのようにトークンが追加されているか見れるでしょう: <form action="/users/1" method="post"> <input type="hidden" value="67250ab105eb5ad10851c00a5621854a23af5489" name="authenticity_token"/> <!-- fields --> </form> Rails は form helper を使用して生成したすべてのフォームに、このトークンを追加するので、 form_authenticity_token は、有効な認証トークンを生成します。 Security Guideは、このことおよび、 10 Request 、Response オブジェクト(The Request and Response Objects) †全てのコントローラー内に、現在実行中のリクエストサイクルに関連付けられた 10.1 request オブジェクト(The request Object) †クライアントから来るリクエストについての便利な情報を、 request オブジェクトは沢山格納しています。
10.1.1 path_parameters 、 query_parameters 、 request_parameters Rails はクエリ文字列の一部か post の body の一部として送られたに関わらず、 10.2 response オブジェクト(The response Object) †response オブジェクトは、通常直接使用されませんが、
10.2.1 カスタムヘッダの設定 response のためのカスタムヘッダーを設定したい場合、 response.headers はちょうどいいです。 response.headers["Content-Type"] = "application/pdf" 11 HTTP認証(HTTP Authentications) †Rails は2つの組み込み HTTP 認証メカニズムを持っています:
11.1 HTTP Basic 認証(HTTP Basic Authentication) †HTTP Basic 認証は、ブラウザとその他の HTTP クライアントの大半でサポートされている認証スキームです。 class AdminController < ApplicationController USERNAME, PASSWORD = "humbaba", "5baa61e4" before_filter :authenticate private def authenticate authenticate_or_request_with_http_basic do |username, password| username == USERNAME && Digest::SHA1.hexdigest(password) == PASSWORD end end end この場所で、 AdminController を継承した namespace 化されたコントローラを作れます。 11.2 HTTP Digest 認証(HTTP Digest Authentication) †HTTP Digest 認証は、ネットワーク越しに暗号化されていない password を送信しろと要求しない点で、 Basic 認証より優れています。 class AdminController < ApplicationController USERS = { "lifo" => "world" } before_filter :authenticate private def authenticate authenticate_or_request_with_http_digest do |username| USERS[username] end end end 上記の例に見られるよう、 authenticate_or_request_with_http_digest ブロックは、引数を username 一つだけ取ります。 12 ストリーミングとファイルのダウンロード(Streaming and File Downloads) †場合によっては、 HTML ページをレンダリングする代わりにユーザーにファイルを送信したいかもしれません。 クライアントにデータを配信するには send_data を使用します: require "prawn" class ClientsController < ApplicationController # クライアント上の情報から PDF 文書を生成し、それを返します。 # ユーザーは ファイルとして PDF をダウンロード出来るでしょう。 def download_pdf client = Client.find(params[:id]) send_data(generate_pdf, :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 文書を作成し、それを文字列をして返す private メソッドを呼び出すでしょう。 12.1 ファイルの送信(Sending Files) †ディスク上に既に存在するファイルを送信したい場合、 send_file メソッドを使用します。 class ClientsController < ApplicationController # 既に生成され、ディスクに保存されたファイルを配信する。 def download_pdf client = Client.find(params[:id]) send_data("#{RAILS_ROOT}/files/clients/#{client.id}.pdf", :filename => "#{client.name}.pdf", :type => "application/pdf") end end これはファイルを読み込んで、メモリに一度にファイル全体を読み込むことを避けるためにファイルを 4KB ずつストリーミングするでしょう。 ディスク上のファイルを探すためにクライアントからの情報( params , cookies 他)を使う場合、 ウェブ上のパブリックフォルダ内のサーバーにファイルを持てる場合、 Rails を介した静的ファイルの配信は推奨されません。 12.2 RESTful なダウンロード(RESTful Downloads) †send_data が正常に動いている間、 RESTful なアプリケーションを作成していれば、 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 タイプを追加する必要があります。 Mime::Type.register "application/pdf", :pdf 設定ファイルは、各リクエスト上で、再読み込みされません。 今すぐユーザーは URL に ".pdf" を追加するだけで、クライアントの PDF バージョンを取得するようにリクエストできます。 GET /clients/1.pdf 13 パラメータフィルタ(Parameter Filtering) †Rails は log フォルダ内に、各環境用のログファイルを保持します。 class ApplicationController < ActionController::Base filter_parameter_logging :password end メソッドは params ハッシュの全てのレベルを通じて再帰的に動作し、 14 rescue(Rescue) †ほとんどのアプリケーションはバグが含まれている可能性が高く、そうでなければ処理する必要がある例外をスローします。 Rails のデフォルトの例外処理は、全ての例外に対して "500 Server Error" を表示します。 14.1 デフォルトの 500 、 404 テンプレート(The Default 500 and 404 Templates) †デフォルトでは、 production アプリケーションは、 404 か 500 エラーメッセージをレンダリングするでしょう。 14.2 rescue_from(rescue_from) †エラーをキャッチするとき、もう少し手の込んだことをしたいなら、 rescue_from を使えます。 例外が発生して rescue_from ディレクティブでキャッチされた時、例外オブジェクトはハンドラに渡されます。 ここに、全ての 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 # ユーザーが顧客にアクセスする正しい権限を持っているか確認します。 before_filter :check_authorization # 全ての認証についてアクションが心配する必要がないことに注意して下さい。 def edit @client = Client.find(params[:id]) end private # ユーザーは、承認されていない場合、ただ例外をスローします。 def check_authorization raise User::NotAuthorized unless current_user.admin? end end コントローラが初期化されアクションが実行される前に raise するような特定の例外は 15 Changelog †
コメント欄(誤訳・誤字があれば教えて頂ければ幸いです。) † |