restful_authenticationを触ってみた

久々にRailsを触っていたら、認証プラグインの定番acts_as_authenticatedが、より新しいrestful_authenticationに変わっていることに気づいた。前者は今後保守されないらしいので、乗り換えは必須。新米RESTafarianとしては、RESTfulを名乗っているところにも興味を引かれる。

そういうわけで触ってみて導入手順を書いたが、RailsのREST実装についての説明も含むので、ちょっと冗長になってしまった。

2010/05/24追記。こちらも参照のこと。
最新版のrestful-authenticationはどこにある? - idesaku blog

インストール

まずはrailsアプリケーション作成。今回は2.0.2を使う。

$ rails authtrial
      create
      create  app/controllers
      create  app/helpers
      create  app/models
      create  app/views/layouts

      …いろいろ出る…

      create  log/development.log
      create  log/test.log
$ cd authtrial

続けて、restful_authenticationプラグインのインストール。ただし、インストール実行前にリポジトリを追加登録しておく必要がある。

$ script/plugin source http://svn.techno-weenie.net/projects/plugins
Added 1 repositories.
$ script/plugin install restful_authentication

作成

認証用のコンポーネント一式を生成する。

$ script/generate authenticated user sessions

userはユーザ情報として使うmodelの名前、sessionsはセッション管理用のコントローラ名。このsessionsの存在がこのプラグインをRESTfulたらしめている部分だ、多分。ログインという"独自の動作"を、"セッションというリソースのPOST"と捉えることで、統一インタフェースとリソース指向が保たれる。

viewだのcontrollerだのと一緒にユーザ情報テーブルのmigrationファイルが作られているので、さっさとDBを作ってしまう。DBはRails 2.0.2デフォルトのままsqlite3を使うので、特にconfig/database.ymlは編集しない。

$ rake db:migrate
(in /home/idesaku/projects/authtrial)
== 1 CreateUsers: migrating ===================================================
-- create_table("users", {:force=>true})
   -> 0.0378s
== 1 CreateUsers: migrated (0.0465s) ==========================================
$

config/routes.rbを編集。次の5行を書き足す。

ActionController::Routing::Routes.draw do |map|
  # この2行はおそらく勝手に追加されている。無ければ書く。
  # sessionsではなくsessionである点に注意。
  map.resources :users
  map.resource :session

  # 次の3行を書く。
  map.signup '/signup', :controller => 'users', :action => 'new'
  map.login '/login', :controller => 'sessions', :action => 'new'
  map.logout '/logout', :controller => 'sessions', :action => 'destroy'

map.resource(s)は、RESTfulな名前付きルートを追加するためのヘルパーメソッド。単数形(resource)の場合はシングルトンリソース向けのルートを定義することになり、複数形(resources)の場合定義されるいくつかのルートが省略される。セッション情報なんて複数同時に扱うこと無いから単数にすべきなのだろうな。

おっと脱線した。

map.signupからの3行も、同様に名前付きルートの定義である。これらは無くてもアプリケーションを動かすことはできるのだが、map.resource(s)がデフォルトで作成するルートのままだとなんだかわかりづらいので、こうして名前を付けてやったほうが直感的でよろしい。*1

ここでapp/controllers/users_controller.rbを見てみる。

class UsersController < ApplicationController
  # Be sure to include AuthenticationSystem in Application Controller instead
  include AuthenticatedSystem

AuthenticatedSystemをここじゃなくてアプリケーションコントローラにincludeしろ、と言ってきているので、その通りにせねばなるまい。そういうわけで、コメントと"include AuthenticatedSystem"の2行を削除する。

そして、今度はapp/controllers/application.rbを開いて、includeさせる。

class ApplicationController < ActionController::Base
  # この一行を追加
  include AuthenticatedSystem

  helper :all # include all helpers, all the time

これで認証用のヘルパーメソッドを全てのコントローラから使えるようになるわけである。

ログイン後にアクセスできるページが欲しいので、コントローラを追加で作成しておく。名前はありがちにwelcomeとする。

$ script/generate controller welcome index

app/views/welcome/index.html.erbを開いて、次のように編集する。

<h1>*よ*う*こ*そ*</h1>
<p><%= current_user.login %>よ、良く来た。まぁ座れ。</p>
<%= link_to 'ログアウト', logout_url %>

ログインに成功した暁には、ユーザ名など表示させつつねぎらってやろうという心意気である。

link_toの引数として与えているlogout_urlは、config/routes.rbで設定した名前付きルートの定義(map.logout...の1行)によって加えられたヘルパーメソッドの一つで、logoutルートの適切なURLを返してくれる。詳しくはAPIリファレンス参照。

おっと、表示に凝る(?)前に、そもそも認証が必要だという設定をしなければならなかった。app/controllers/welcome_controller.rbを、次のように編集する。

class WelcomeController < ApplicationController
  # この一行を追加
  before_filter :login_required

これでこのコントローラのアクションにアクセスするためには認証を経なければならなくなった。

最後に、ちゃんとデフォルトでwelcomeコントローラを見に行くように設定する。まず、config/routes.rbに次の一行を加える。

  map.connect ':controller/:action/:id'
  map.connect ':controller/:action/:id.:format'

  # この一行を書き加える
  map.root :controller => "welcome"
end

そして、public/index.htmlを削除する。これをやっておかないと、Railsは問答無用でこのファイルを表示させてしまうのだ。

動かしてみる

それでは、サーバを起動して動作を見てみる。

$ script/server

ブラウザでhttp://localhost:3000/にアクセス。次のように、ログイン画面が表示されればとりあえず成功したと思ってよい。welcome#indexを表示しようとしたが、ログインが要求されたのでログイン画面に飛ばされたのである。

もっとも、今はまだユーザを作っていないのでログインできない。まずはサインアップ画面でユーザを作成しなければならない。そこで、http://localhost:3000/signupにアクセスする。本当はログイン画面にサインアップ画面へのリンクを書いておくべきなのだろうが、面倒なので今回はやらない。

見ての通りの画面なので、適当な値を入力してSign upボタンを押し、ユーザを作成する。うまくいけば、同時にログインも実行され、Welcome画面が表示されるはずだ。

ログアウトリンクをクリックすれば、ログアウトが実行され、再びログイン画面に戻る。もちろん、先ほどサインアップしたユーザで再びログイン可能だ。

蛇足

最初のほうで実行したgenerateコマンドなのだが。

$ script/generate authenticated user sessions

これに渡すセッション管理コントローラの名前(この場合sessions)は、どうやら必ず複数形にしなければならないらしい。単数形の名称、例えばlogin_infoなんて名前を与えてみると、生成されるのはLoginInfoControllerだというのに認証時はLoginInfosControllerを参照しようとしてエラーになる。

これは、config/routes.rbでmap.resource :login_infoと定義される部分に原因があるようだ。map.resourceは、名前付きルートに設定するコントローラの名前として、与えられたリソース名の複数形を使う。ところが、generateで単数形の名前を与えるとコントローラの名前も単数形になるため整合性が取れなくなるのだ。

config/routes.rbで次のように書いてコントローラ名を明示的に指定すれば、単数形でも使える。

  map.resource :login_info, :controller => 'login_info'

でも、こんな小手先の技使うより、素直に複数形にしておいたほうがいい。極力Railsに逆らわない、それがRailsを使う上での鉄則である。

終わり

restful_authentication自身より、むしろRails2.0のREST実装の理解に苦戦した。

restful_authenticationにはactivation(Sign up時にメールを送ってくる、よくあるやり方)やstateful認証を行うための機能もあるのだが、今回は触れていない。気が向いたらそのうち扱うかも知れない。

*1:#67 restful_authentication - RailsCastsでこう書いていたのをマネしているのだが、RESTとしてはどーなのかね、これ?せっかくリソース指向にしたのが台無し?それとも、リソースとその表現を分離する、という点からすれば正しいのか?