URLの設計

昨日は、jascaffoldで作成したresevationコントローラをいろいろいじりながら Rails の勉強をしました。断片的な作業は進んでいるのですが、確認をしてないので、ここからテストを作って確認しながら、コントローラとビューを開発していきたいと思います。

まず、全体のURLとコントローラについてまとめてみます。このシステムでは、以下のコントローラを作成します。

reservetion
カウンセラーのみが操作できるコントローラ。CounselingSessionというトランザクションデータのCRUD(Create, Read, Update, Delete)を行なう。
client
LoginEngineのUserControllerを継承して作成したユーザ管理用のコントローラ。ClientというマスターデータのCRUDとログイン、ログアウトを行なう。
main
ゲスト用の表示、クライアント用の表示と予約、キャンセル等の業務処理を行なう。
test_login
LoginEngine等のテスト

それで、役割別の流れを言うと、最初にルートにアクセスした時には、初期画面として空き状態にあるカウンセリングセッションを一覧表示します。これはゲストにも見える情報のみで構成されていて、ゲストとして利用できるのは、この画面だけです。この画面は main/index とします。

はじめて予約しようとする人は、表示された空き時間の中から、希望する時間に対応した「予約」というリンクをクリックします。そこから自動的にログイン画面にリダイレクトされて、さらにそこで「はじめての人はユーザ情報を登録してください」というリンクをクリックして、自分のユーザ情報を登録します。この流れは、clientコントローラの中で処理されます。

登録済のユーザは、ログインするとmain/index に戻りますが、ここはゲストでなくクライアント向けの表示になっていて、空き情報に加え、自分が予約した情報も見ることができます。

ログインしたユーザは、その一覧から「予約」と「キャンセル」の操作ができます。ここは、mainコントローラの処理になります。

カウンセラーは、ログインすると、reservetionコントローラにリダイレクトされ、こちらからは、全ての操作ができます。

ざっとこんな流れで、アクション名は開発しながら考えていきます。

では、mainコントローラを作って、ルートからそこに飛ぶような設定を行なってみましょう。

$ script/generate controller main   
      exists  app/controllers/
      exists  app/helpers/
      create  app/views/main
      exists  test/functional/
      create  app/controllers/main_controller.rb
      create  test/functional/main_controller_test.rb
      create  app/helpers/main_helper.rb
class MainController < ApplicationController

  def index
    render_text 'main'
  end
end

これで、http://localhost:3000/mainにアクセスすると "main" という文字が表示されます。http://localhost:3000/でここに飛ぶようにする為に、config/routes.rbを変更します。

ActionController::Routing::Routes.draw do |map|
  map.connect ':controller/service.wsdl', :action => 'wsdl'

  # Install the default route as the lowest priority.
  map.connect ':controller/:action/:id'
  map.connect '', :controller => 'main', :action=>'index' # ← 追加
end

そして、public/index.htmlを削除して、http://localhost:3000/にアクセスすると、"main"が表示されました。

ついでに、デバッグ用の余計なコントラーラにアクセスしないような設定をしてみます。

ActionController::Routing::Routes.draw do |map|
  map.connect '', :controller => 'main', :action=> 'index'
  if ENV['RAILS_ENV'] == "production" # 本番モードでは指定したコントローラ以外にアクセスできないようにする
    map.connect ':controller/:action/:id',  :requirements => { :controller => /main|client|reservation/ }
  else
    map.connect ':controller/:action/:id'
  end
  map.connect '*anything', :controller => 'main', :action => 'unknown_request'
end

こうすると本番モードで起動した時には、main client reservation という3つのコントローラ以外にはアクセスできないようになります。Railsの設定ファイルはRubyスクリプトなので、こういう時に小回りがきいていいですね。それ以外のURLでアクセスされた時には、unknown_requestというアクションが起動されるので、下記のようにエラー処理用のメソッドを追加します。

class MainController < ApplicationController
  def index
    render_text 'main'
  end

  def unknown_request
    render_text '不正なURLです'
  end
end

下記のように試しに本番モードで起動してから、http://localhost:3000/user/http://localhost:3000/test_login/にアクセスして、エラーとなることを確認しておきます。

$ script/server -eproduction

実はここで、database.ymlの設定を忘れていてエラーとなっていたのですが、routes.rbの間違いだと思いこんで、それを直してもうまくいかず、かなりハマってしまいました。本番モードではブラウザへのエラー表示が最低限になるので、原因がわかりにくいですね。log/production.logを見てようやく気がつきました。でも、情報を出さないのはセキュリティ的な配慮だからしょうがないというか、むしろ好ましいわけで、ログを見なかった私が悪い。

それでは、ここからテストファース(風味)で作っていきますので、まず生成されたままのコントローラのテストを実行してみます。

$ ruby test/functional/main_controller_test.rb
Loaded suite test/functional/main_controller_test
Started
.
Finished in 0.020338 seconds.

1 tests, 1 assertions, 0 failures, 0 errors||<

これは、次のような当然成功するテストをやっているだけです。

  def test_truth
    assert true
  end

これを次のように変更します。同時に、couseling_sessionsのfixtureも加えます。

  def test_index
    get :index
    assert_response :success
    assert_match /free1/, @response.body
    assert_match /free2/, @response.body
  end

これはセッション情報が無いので、ゲストとしてmain/indexにアクセスしたことになります。ここには、空き情報が表示されるはずなので、fixtureで設定したデータのメモの欄にある "free1" "free2" というテキストがレスポンスには含まれているはずです。それをテストしています。

$ ruby test/functional/main_controller_test.rb
Loaded suite test/functional/main_controller_test
Started
F
Finished in 0.068964 seconds.

  1) Failure:
test_index(MainControllerTest) [test/functional/main_controller_test.rb:19]:
<"main"> expected to be =~
</free1/>.

1 tests, 2 assertions, 1 failures, 0 errors

実行するとこのようにエラーになりました。ここから、業務の流れにそって、テストファースト(風味)で開発していこうと思います。