restful_authentication を題材にして routes, resource and resources のお勉強

(追記: この記事よりも、ActiveResourceとかそのルーティングとかのドキュメント - moroの日記 を読んだ方が良いと思います!)
(追記その2: restful_authentication の controller_name がバグってたのが直ったようですね。デフォルトも sessions になってます。ですので、以下の記事の session_controller まわりの記事は現在では不必要になってます)

大幅に書き換えました。hatena.vim rocks.

インストール

% cd your_rails_project
% ./script/plugin install http://svn.techno-weenie.net/projects/plugins/restful_authentication/

controller の作成

% ./script/generate authenticated model_name [controller_name]

controller_name は省略可能(デフォルトは session)です。今回は model_name を user、controller_name を session にします。

% ./script/generate authenticated user

これで users_controller と session_controller が作られます。モデルなどもできますが、今回は触れません。

RESTful なコントローラにする

まずは users_controller と session_controller を RESTful なコントローラにします。config/route.rb を編集します。

ActionController::Routing::Routes.draw do |map|
  map.resources :users
  map.resource :session
end

map.resource と map.resources の違いについては、http://api.rubyonrails.org/classes/ActionController/Resources.html を参照すると良いと思います。session_controller には :id が必要ない(ログインとログアウトだけに使う)ので、singleton resource を選択しました。この時点で

% rake routes

を行うと、現時点での routes 設定を見ることができます。

users GET /users {:controller=>"users", :action=>"index"}
formatted_users GET /users.:format {:controller=>"users", :action=>"index"}
POST /users {:controller=>"users", :action=>"create"}
POST /users.:format {:controller=>"users", :action=>"create"}
new_user GET /users/new {:controller=>"users", :action=>"new"}
formatted_new_user GET /users/new.:format {:controller=>"users", :action=>"new"}
edit_user GET /users/:id/edit {:controller=>"users", :action=>"edit"}
formatted_edit_user GET /users/:id/edit.:format {:controller=>"users", :action=>"edit"}
user GET /users/:id {:controller=>"users", :action=>"show"}
formatted_user GET /users/:id.:format {:controller=>"users", :action=>"show"}
PUT /users/:id {:controller=>"users", :action=>"update"}
PUT /users/:id.:format {:controller=>"users", :action=>"update"}
DELETE /users/:id {:controller=>"users", :action=>"destroy"}
DELETE /users/:id.:format {:controller=>"users", :action=>"destroy"}
POST /session {:controller=>"sessions", :action=>"create"}
POST /session.:format {:controller=>"sessions", :action=>"create"}
new_session GET /session/new {:controller=>"sessions", :action=>"new"}
formatted_new_session GET /session/new.:format {:controller=>"sessions", :action=>"new"}
edit_session GET /session/edit {:controller=>"sessions", :action=>"edit"}
formatted_edit_session GET /session/edit.:format {:controller=>"sessions", :action=>"edit"}
session GET /session {:controller=>"sessions", :action=>"show"}
formatted_session GET /session.:format {:controller=>"sessions", :action=>"show"}
PUT /session {:controller=>"sessions", :action=>"update"}
PUT /session.:format {:controller=>"sessions", :action=>"update"}
DELETE /session {:controller=>"sessions", :action=>"destroy"}
DELETE /session.:format {:controller=>"sessions", :action=>"destroy"}

session_controller の方の :controller 設定が "sessions" になっているのでこれを修正します。"sessions" になっている理由は rails 2.0 で導入された new convention のためだと思います。

We’ve also instigated a new convention that all resource-based controllers will be plural by default

Rails 2.0: It’s done! | Riding Rails

session_controller を見るように変更する

config/routes.rb に書いた先ほどの設定を変更します。

ActionController::Routing::Routes.draw do |map|
  map.resources :users
  map.resource :session, :controller => "session"
end

もう一度 routes 設定を確認します(session部分のみ掲載)。

POST /session {:controller=>"session", :action=>"create"}
POST /session.:format {:controller=>"session", :action=>"create"}
new_session GET /session/new {:controller=>"session", :action=>"new"}
formatted_new_session GET /session/new.:format {:controller=>"session", :action=>"new"}
edit_session GET /session/edit {:controller=>"session", :action=>"edit"}
formatted_edit_session GET /session/edit.:format {:controller=>"session", :action=>"edit"}
session GET /session {:controller=>"session", :action=>"show"}
formatted_session GET /session.:format {:controller=>"session", :action=>"show"}
PUT /session {:controller=>"session", :action=>"update"}
PUT /session.:format {:controller=>"session", :action=>"update"}
DELETE /session {:controller=>"session", :action=>"destroy"}
DELETE /session.:format {:controller=>"session", :action=>"destroy"}

これで、:controller が "session" を見るようになりました。

ログイン、ログアウト、サインアップ用の url を用意する

動作としてはこれで良いのですが、

http://example.jp/login でログイン
http://example.jp/logout でログアウト
http://example.jp/signup でサインアップ

にしたいと思いましたので、config/routes.rb をもう少し変更します。
Routing の基本は http://api.rubyonrails.org/classes/ActionController/Routing.html で知ることができるので、それを見るとだいたいわかると思います。

ActionController::Routing::Routes.draw do |map|
  map.connect "login", :controller => "session", :action => "new"
  map.connect "logout", :controller => "session", :action => "destroy"
  map.connect "signup", :controller => "users", :action => "new"
  map.resources :users
  map.resource :session, :controller => "session"
end

ビューで楽をするために Named routes を利用する

あとは、ビューで link_to などで /login などに飛びたいときにいちいち :controller などを書くのがめんどいので、login_path とか logout_path とかで表現できるようにすると楽です。そのために、map.connect ではなく、map._name_of_root_ を使います。

ActionController::Routing::Routes.draw do |map|
  map.login "login", :controller => "session", :action => "new"
  map.logout "logout", :controller => "session", :action => "destroy"
  map.signup "signup", :controller => "users", :action => "new"
  map.resources :users
  map.resource :session, :controller => "session"
end

たとえば先頭の

  map.login "login", :controller => "session", :action => "new"

を例にとると、こう設定することで、login_url, login_path を利用できるようになります。詳しくは http://api.rubyonrails.org/classes/ActionController/Routing.html の Named routes の項を読むと良いと思います。

Don't Repeat Yourself

:controller => "session" と何度も書くのが嫌なので、まとめてしまいます。with_options を利用します。

ActionController::Routing::Routes.draw do |map|
  map.with_options :controller => "session" do |page|
    page.login "login", :action => "new"
    page.logout "logout", :action => "destroy"
    page.resource :session
  end
  map.signup "signup", :controller => "users", :action => "new"
  map.resources :users
end

session_controller と users_controller の routes 設定の順番が変わってしまいましたが、rake routes で確認したところかぶってるところもないので問題なさそうです。一応結果を表示しておきます。

login /login {:controller=>"session", :action=>"new"}
logout /logout {:controller=>"session", :action=>"destroy"}
POST /session {:controller=>"session", :action=>"create"}
POST /session.:format {:controller=>"session", :action=>"create"}
new_session GET /session/new {:controller=>"session", :action=>"new"}
formatted_new_session GET /session/new.:format {:controller=>"session", :action=>"new"}
edit_session GET /session/edit {:controller=>"session", :action=>"edit"}
formatted_edit_session GET /session/edit.:format {:controller=>"session", :action=>"edit"}
session GET /session {:controller=>"session", :action=>"show"}
formatted_session GET /session.:format {:controller=>"session", :action=>"show"}
PUT /session {:controller=>"session", :action=>"update"}
PUT /session.:format {:controller=>"session", :action=>"update"}
DELETE /session {:controller=>"session", :action=>"destroy"}
DELETE /session.:format {:controller=>"session", :action=>"destroy"}
signup /signup {:controller=>"users", :action=>"new"}
users GET /users {:controller=>"users", :action=>"index"}
formatted_users GET /users.:format {:controller=>"users", :action=>"index"}
POST /users {:controller=>"users", :action=>"create"}
POST /users.:format {:controller=>"users", :action=>"create"}
new_user GET /users/new {:controller=>"users", :action=>"new"}
formatted_new_user GET /users/new.:format {:controller=>"users", :action=>"new"}
edit_user GET /users/:id/edit {:controller=>"users", :action=>"edit"}
formatted_edit_user GET /users/:id/edit.:format {:controller=>"users", :action=>"edit"}
user GET /users/:id {:controller=>"users", :action=>"show"}
formatted_user GET /users/:id.:format {:controller=>"users", :action=>"show"}
PUT /users/:id {:controller=>"users", :action=>"update"}
PUT /users/:id.:format {:controller=>"users", :action=>"update"}
DELETE /users/:id {:controller=>"users", :action=>"destroy"}
DELETE /users/:id.:format {:controller=>"users", :action=>"destroy"}