StoneDot の Ruby on Rails 講座

コントーローラーとルーティング

今回はコントローラーが一体何をしているのか、そしてルーティングについて解説していきたいと思います。 Action Controller Overview と、 Rails Routing from the Outside In を参考にしました。

コントローラーの概要

まず、 Rails のコンポーネントの一つである、 Action Controller は MVC の C に当たることは初回に説明しました。 対してどのコントローラがリクエストの処理を行うか決めることをルーティングといいますが、これを決めるのは Action Routing の仕事です。

コントローラの仕事はリクエストの意味を理解し、それに合わせて適切な処理、出力を行うことです。

そのために Rails は様々な道具を用意してくれています。ここからはその道具について見ていきます。

メソッドとアクション

Rails でコントローラーを作成するには、 ApplicationController を継承したクラスを作成します。 コントローラー内にある公開されているメソッドはアクションと呼ばれていて、 ルーティングではどのコントローラでどのアクションを実行するべきなのかを決定します。 その後、 Rails はコントローラのインスタンスを作成し、ルーティングで決定されたメソッドをアクションとして実行します。

ApplicationController は Rails アプリケーションを作成する際に自動的に作成されるクラスで、 アプリケーション内のすべてのコントローラに適用したいこと(認証など)は ApplicationController に記述すると良いでしょう。

ApplicationController 自体は ActionController::Base を継承しているため、すべてのコントローラは ActionController::Base のサブクラスと言うことが出来ます。

class CustomersController < ApplicationController
  def new
  end
end

上記のコードは CustomersController というコントローラの中に new というアクションを定義する例です。 以上のようにメソッドを作成するだけでアクションを作ったことになります。

Rails では規約によりレンダリングするビューを指定していない場合は app/views/{コントローラ名}/{アクション名.html.erb} をビューとして使用することになっています。今回の場合だと、 app/views/customers/new.html.erb がビューとして使われます。

コントローラ内の公開されているメソッドのみがアクションとして扱われることに注意してください。 逆に言うと、外部に公開すると問題のあるメソッドは public にしないようにしましょう。

パラメータ

Web アプリケーションではユーザーからデータを受け取るために GET リクエストのクエリや、 POST リクエストのデータを利用します。 これらがどのような形式で渡されるかは HTML フォームの形式から決定されるのが普通です。

しかし、 Rails 内でパラメータを扱う際にはどちらのメソッドで送られてきたデータなのかを区別はしません。 GET のパラメータも POST のパラメータも params という連想配列(ハッシュ)に入っています。

リクエストが GET だったのか POST だったのか知りたい場合は request.post?request.get? を利用します。

例えばパラメータを利用して顧客の表示順を変えるコードは以下のものが考えられます。

class CustomersController < ApplicationController
  def index
    if params[:order] == "id_asc" # order=id_asc というクエリが含まれていた場合
      @customers = Customer.order("customer_id ASC") # 顧客IDで昇順に並び替えた顧客の配列を取得
    elsif params[:order] == "id_desc" # order=id_desc というクエリが含まれていた場合
      @customers = Customer.order("customer_id DESC") # 顧客IDで降順に並び替えた顧客の配列を取得
    else
      @customers = Customer.order("created_at") # 顧客登録された順で顧客の配列を取得
    end
  end
end

?order=id_asc などのクエリを含むリクエストを作成する HTML としては以下のようなものが考えられます。

<form action="/customers" method="get">
  <select name="order">
    <option value="created_date" selected>既定</option>
    <option value="id_asc">▲ ID</option>
    <option value="id_desc">▼ ID</option>
  </select>
  <input type="submit" value="並び替え">
</form>

params の構造

params に格納されるオブジェクトには大きく2種類あります。一つがクエリから生成されるクエリパラメータ(POSTの場合もこれに含む)。 もう一つが URL からルーティングルールに基づいて自動的に追加されるものルーティングパラメータです(このような文脈でのクエリパラメータという言葉の使い方は一般的ではないと思うので他の人に伝わらない可能性があります)。

params に格納するオブジェクトは文字列だけでなく、配列や連想配列にすることも出来ます。

たとえ文字列に含まれるのが数字だけだとしても、Rails はそれらを Integer に変換したりすることはありません。

クエリパラメータ

ここでは、クエリパラメータについて説明します。

クエリを name=val とした場合は params[:name] で val となっている文字列にアクセス出来ます。

配列を params に格納するためには以下のようなクエリを送信する必要があります。

?list[]=1&list[]=4&list[]=7&list[]=8

この配列には params[:list] とすることでアクセスすることが出来、 これによって得られるのは ["1", "4", "7", "8"]となります。 取得できるオブジェクトは数字ではなく文字列になっていることに気をつけてください。

params に連想配列を格納するためには以下のようなクエリが必要です。

?customer[customer_id]=93&customer[name]=John&customer[phone]=1234-567-8901&customer[email[]]=j1@example.com&customer[email[]]=j2@example.com

上記のようなクエリを渡すと params[:customer]{:customer_id => "93", :name => "John", :phone => "1234-567-8901", :email => ["j1@example.com", "j2@example.com"]} のような連想配列に変換されます。ここでは連想配列の中に配列を持たせていますが、同様に連想配列の中に連想配列を入れることも出来ます。

上記のようなハッシュを含むクエリを生成する HTML の例は以下のとおりです。

<form action="customers" method="post">
  <div><label>
    顧客ID
    <input type="number" name="customer[cuscustomer_id]">
  </label></div>
  <div><label>
    顧客名
    <input type="text" name="customer[name]">
  </label></div>
  <div><label>
    電話番号
    <input type="tel" name="customer[phone]">
  </label></div>
  <div><label>
    メールアドレス
    <input type="email" name="customer[email[]]">
  </label></div>
  <div><label>
    メールアドレス(予備用)
    <input type="email" name="customer[email[]]">
  </label></div>
  <div><input type="submit" value="登録"></div>
</form>

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

params にはルーティングで定義された id が利用可能です。

例えば、 posts 一覧のから一つの post の詳細を閲覧したい場合は、URLにて posts/9517 のように id を指定します。このときの post の id を取得するのに params[:id] を利用することが出来ます。

セッション

セッションを使って小さなデータをリクエストをまたいで保存することが出来ます。一度使ったら利用しないようなものの場合は、フラッシュを利用します。

セッションを実現するための実装はいくつかありますがデフォルトだとクッキーを利用します。クッキーを利用したセッションでもデータが不正に変更されていないことを保証することが出来ます。しかし、保存されているクッキーは暗号化されていないので注意が必要です。また、クッキーを利用した場合は最大4kBまでしかデータを保存することが出来ません。また、クッキーなので消える場合があることに注意してください。

セッションへのアクセス

セッションを利用するのは簡単で、 session というハッシュのようなオブジェクトがあるのでそこにデータを入れたり出したりするだけです。

セッションに値を入れる時は普通にハッシュに値を入れるときのように、

session[:account_id] = params[:id]
といった形で利用します。

現在のセッションからオブジェクトを取り出すためには、

@account = Account.find(session[:account_id])
のような形で記述します。普通のハッシュオブジェクトと同じです。

セッションからデータを削除したい場合は、

session[:account_id] = nil
といったように、 nil を代入します。

もしセッションをリセットしたくなった場合は reset_session を使います。

フラッシュ

フラッシュはセッションの機能の一つで、リクエストが送られるたびに削除されます。つまり、次のリクエストでのみ値を取り出すことができます。

これは例えば警告メッセージを表示したりするのに役立ちます。

session と同様にハッシュのような形で利用します。例えば以下のようにして利用します。

flash[:notice] = "Welcome our website." # 代入
<%= flash[:notice] %> <!-- 値の利用 -->

もし次のリクエストでも現在と同じフラッシュを利用したい場合は flash.keep を利用します。一部だけを持ち越したい場合は flash.keep(:notice) のように引数に持ち越したい値のキーを指定します。

フィルター

フィルターはアクションの前、後、または前後に任意のメソッドを呼び出すための仕組みです。

フィルターは継承されるので、application_controller に記述したフィルターはアプリケーション全体に適用されることになります。

Before フィルター

Before フィルターでリダイレクトをするとリクエストの処理を早い段階で終えることが出来ます。

例えば、認証機能を追加するときに 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

もし、before_filter を実行したくない場合は、

skip_before_filter :require_login, :only => [:new, :create]
のように記述します。これによって、 new と create 以外のアクションには require_login フィルタが実行されなくなります。

After フィルターと Around フィルター

After フィルターはアクションの処理が終わった後に呼ばれるフィルターです。

After フィルターではアクションの実行をやめさせることは出来ません。その代わりにリスポンスデータにアクセスすることが出来ます。

Around フィルターを使うとアクションの前後に処理を入れることが出来ます。

Around フィルターは yield を使ってアクションを呼び出す責任があります。

リソースルーティング

リソースのルーティングを行うためには config/routes.rb に設定を書き込む必要があります。といっても、 Rails のデフォルトのルーティングを行う場合は掲示板の作成行ったように、

resouces :posts
のような行を追加するだけで行うことが出来ます。

通常のリソース

一般的なリソースへのルーティングの設定を行うには以下のように記述します。

resources :posts

この場合、posts_controller.rb のコントローラーに以下のようにルーティングされるようになります。

メソッドパスアクション利用用途
GET/postsindex投稿の一覧を表示する
GET/posts/newnew新しい投稿を作成するためのページを返す
POST/postscreate新しい投稿を作成する
GET/posts/:idshow指定した投稿を表示する
GET/posts/:id/editedit指定した投稿を編集するためのページを返す
PUT/posts/:idupdate指定した投稿を更新する
DELETE/posts/:iddestroy指定した投稿を削除する

また、以下のようなパスを返すヘルパーメソッドが自動的に追加されます。

それぞれの _path ヘルパーメソッドに対応して _url ヘルパーメソッドも追加されます。

単一リソース

アプリケーションを作成していると一つしか存在しない情報にアクセスすることがあります。すなわちIDを必要としないリソースです。例えば、現在ログインしているユーザーの情報を表示するページにはIDが必要ありません。このリソースに /profile でアクセスすることが出来るようにするためには以下のように記述します。

resource :profile

このような記述を行うことによって以下のように profiles_controller.rb のアクションにルーティングされるようになります。

メソッドパスアクション利用用途
GET/profile/newnew新しいプロファイルを作成するためのページを返す
POST/profilecreate新しいプロファイルを作成する
GET/profileshowプロファイルを表示する
GET/profile/editeditプロファイルを編集するためのページを返す
PUT/profileupdateプロファイルを更新する
DELETE/profiledestroyプロファイルを削除する

この方法でルートを作成すると以下のようなヘルパーメソッドでURLを生成できるようになります。

ネストしたリソース

あるリソースが論理的にほかのリソースを持つという状況はよくあることだと思います。

例えばブログの記事にはふつうコメントを付けることができますが、これは論理的にはコメントはブログの子であるととらえることが出来ます。

このような構造を Rails で実現するためにはリソースのネストを利用します。掲示板の作成でも同様のことを行っています。

class Post < ActiveRecord::Base
  has_many :comment
end

class Comment < ActiveRecord::Base
  belongs_to :post
end

上記のように定義されたモデルをルーティング的にもネストさせるためには、以下のように config/routes.rb に記述します。

resouces :posts do
  resources :comment
end

このように記述することによって posts のルーティングに加えて以下のような CommentsController へのルーティングルールが作成されます。この時、コメントにアクセスするためには投稿のIDが必須になります。

メソッドパスアクション利用用途
GET/posts/:post_id/commentsindexある投稿へのコメントの一覧を表示する
GET/posts/:post_id/comments/newnewある投稿への新しいコメントを作成するためのページを返す
POST/posts/:post_id/commentscreateある投稿への新しいコメントを作成する
GET/posts/:post_id/comments/:idshowある投稿への指定したコメントを表示する
GET/posts/:post_id/comments/:id/editeditある投稿への指定したたコメントを編集するためのページを返す
PUT/posts/:post_id/comments/:idupdateある投稿への指定したコメントを更新する
DELETE/posts/:post_id/comments/:iddestroyある投稿への指定したコメントを削除する

これによって以下に挙げるようなヘルパーメソッドが追加されます。

オブジェクトからパスの生成

上記のようなネストしたルーティング状態において、以下のような方法でオブジェクトにアクセスするためのパスを生成することが出来ます。

まず、@post に Post のインスタンスと @comment に Comment のインスタンスが格納されているとします。

post_comment_path を利用する際に引数にIDを指定する代わりに以下のように記述することが出来ます。

<%= link_to "View comment", post_comment_path(@post, @comment) %>

このような場合は url_for を利用することも出来ます。

<%= link_to "View comment", url_for([@post, @comment]) %>

このように書くと Rails が自動的にどのヘルパーを使うべきなのかを判断してくれます。

さらに、link_to などのメソッドでは url_for も省略出来ます。

<%= link_to "View comment", [@post, @comment] %>

これが掲示板を作成する際に利用した形式です。

もし、投稿のページを表示したい場合は、

<%= link_to "View post", @post %>
といった形で書くことが出来ます。

RESTfulなアクションの追加

もし、リソースにデフォルト以外のアクションを追加したくなった場合は以下のように記述します。

resources :post do
  member do
    get :preview
  end
  resources :comment
end

上記のように書くことで、 /posts/:post_id/preview という GET メソッドを Post コントローラーの preview アクションにルーティングするようになります。

この時ヘルパーメソッドとして preview_post_url, preview_post_path が追加されます。

これは以下のように書くこともできます。

resources :post do
  get 'preview', :on => :member
  resources :comment
end

もしコレクション全体に対するルーティングを追加したい場合は以下のようにします。

resources :post do
  collection do
    get :search
  end
  resouces :comment
end

このように記述することによって、 /posts/search という GET メソッドを Post コントローラーの search アクションにルーティングすることが出来ます。

同様にヘルパーメソッドとして、 search_posts_url, search_posts_path が使えるようになります。

これは以下のように記述することもできます。

resources :post do
  get 'search', :on => :collection
  resouces :comment
end