[coreserve]予約とキャンセル

一番重要な予約とキャンセルの処理を実装しました。

実際の作業は、(いろいろな割り込みもあったので)もうちょっと行きつ戻りつしていますが、基本的にはテストを書いて実装して、Webrickサーバで動かして調整しての繰り返しでやりました。

まず、ログインしてからの一覧表示のテスト。ここで作成したfixtureを使用しています。

  def test_index_for_client
    get :index, {}, :user=>@cl1
    assert_response :success

    # ログアウトへのリンクを表示しているか
    assert_tag 'a', :attributes => { :href => '/client/logout' }

    # 空きデータを表示しているかメモ欄のテキストで確認
    assert_match /free1/, @response.body
    assert_match /free2/, @response.body

    # 予約済データを表示しているかメモ欄のテキストで確認
    assert_match /reserved1/, @response.body

    # コントローラの変数に設定された空きデータの件数確認
    assert_equal 3, assigns("counseling_sessions").size

    # コントローラの変数に設定された空きデータのステータス確認
    assigns("counseling_sessions").each do |c|
      assert((c.status == :reserved and c.client == @cl1) || (c.status == :free ))
    end
    # 予約へのリンクがあるか
    assert_tag 'a', :attributes => { :href => '/main/reserve/10001' }
    assert_tag 'a', :attributes => { :href => '/main/reserve/10002' }
    # キャンセルへのリンクがあるか
    assert_tag 'a', :attributes => { :href => '/main/cancel/10003' }
  end

実装

class MainController < ApplicationController
  def index
    store_location

    list_param = {
      :per_page => 10,
      :order=>'start desc',
    }
    case current_user
    when nil, Counselor
      list_param[:conditions] = ['status_code = ?',
        CounselingSession::StatusToStatusCode[:free]
      ]
    when Client
      list_param[:conditions] = ['status_code = ? or (client_id = ? and status_code = ?)',
        CounselingSession::StatusToStatusCode[:free],
        current_user.id,
        CounselingSession::StatusToStatusCode[:reserved]
      ]
    end
    @counseling_session_pages, @counseling_sessions = paginate :counseling_sessions, list_param
  end
  ....

ビュー

<h1>カウンセリング予約システム</h1>
<br />
<p>
<%= "#{current_user.lastname} さん " if current_user %>
カウンセリング予約システムへようこそ
</p>
<ul>
<%- case current_user -%>
<%- when nil -%>
  <li><%= link_to 'ログイン', :controller => 'client', :action => 'login' %></li>
  <li><%= link_to '新規ユーザ登録', :controller => 'client', :action => 'signup' %></li>
<%- when Client -%>
  <li><%= link_to 'ログアウト', :controller => 'client', :action => 'logout' %></li>
  <li><%= link_to 'ユーザ情報変更', :controller => 'client', :action  => 'edit' %></li>
<%- when Counselor -%>
  <li><%= link_to 'ログアウト', :controller => 'client', :action => 'logout' %></li>
  <li><%= link_to 'ユーザ情報変更', :controller => 'client', :action  => 'edit' %></li>
  <li><%= link_to '管理', :controller => 'reservation', :action => 'index' %></li>
<%- end -%>
</ul>

<p>現在予約可能な時間帯は以下の通りです</p>
<hr />

<div class="indent">
<table class="confirm">
  <tr>
  <th>カウンセラー</th>
  <th><%= human_attribute_name(CounselingSession, 'status') %></th>
  <th><%= human_attribute_name(CounselingSession, 'start') %></th>
  <th><%= human_attribute_name(CounselingSession, 'end') %></th>
  <th><%= human_attribute_name(CounselingSession, 'place') %></th>
  <th><%= human_attribute_name(CounselingSession, 'memo') %></th>
  <th><br></th>
  </tr>

<%- for counseling_session in @counseling_sessions -%>
  <tr>
    <td><%=h counseling_session.counselor_name %></td>
    <td><%=h counseling_session.status_j %></td>
    <td><%=h human_attribute_value(counseling_session, 'start') %></td>
    <td><%=h human_attribute_value(counseling_session, 'end') %></td>
    <td><%=h human_attribute_value(counseling_session, 'place') %></td>
    <td><%=h human_attribute_value(counseling_session, 'memo') %></td>
<%- case counseling_session.status -%>
<%- when :free -%>
  <%- case current_user -%>
  <%- when nil,Client -%>
    <td><%= link_to '予約', :action => 'reserve', :id => counseling_session %></td>
  <%- else -%>
    <td />
  <%- end -%>
<%- when :reserved -%>
  <%- if current_user == counseling_session.client -%>
    <td><%= link_to 'キャンセル', :action => 'cancel', :id => counseling_session %></td>
  <%- else -%>
    <td />
  <%- end -%>
<%- else -%>
    <td />
<%- end -%>
  </tr>
<%- end -%>
</table>
<br />
<%= pagination_links @counseling_session_pages %>
<br />
</div>

画面イメージ

それで、ここで「予約」のリンクをクリックすると、確認後に予約します。

ここのテスト。

  # ログインしないでここに来たらログインさせる
  def test_reserve_for_guest
    get :reserve, :id=>10001
    assert_redirected_to :controller=>'client', :action => 'login'
  end

  # 確認画面の表示
  def test_reserve_confirm_for_client
    get :reserve, { :id=>10001 }, :user=>@cl1
    assert_match /free1/, @response.body
    assert_match /予約しますか/, @response.body
  end

  # 予約の実行
  def test_reserve_for_client
    post :reserve, { :id=>10001, :commit=>'予約する' }, :user=>@cl1
    assert_match /free1/, @response.body
    assert_match /予約しました/, @response.body

    assert assigns(:counseling_session)
    assert_equal assigns(:counseling_session), CounselingSession.find(10001)
    assert_equal :reserved, assigns(:counseling_session).status
    assert_equal @cl1.id, assigns(:counseling_session).client_id
    assert_equal @cl1, assigns(:counseling_session).client
  end

  # 予約済データを予約しようとしてエラー
  def test_reserve_for_client_error
    assert_equal @cl1, CounselingSession.find(10003).client
    post :reserve, { :id=>10003, :commit=>'予約する' }, :user=>@cl2
    assert_match /予約できません/, @response.body

    assert assigns(:counseling_session)
    assert_equal :reserved, assigns(:counseling_session).status
    assert_equal @cl1, assigns(:counseling_session).client
    assert_equal @cl1, CounselingSession.find(10003).client
  end

実装。これは、テストメソッド一つ作ってはその分だけ実装していきました。

  def reserve
    case current_user
    when nil, Counselor
      store_location
      flash[:notice] = "予約するにはログインが必要です"
      redirect_to :controller=>'client', :action => 'login'
    when Client
      @counseling_session = CounselingSession.find(params[:id])
      unless params[:commit]
        flash[:notice] = "このセッションを予約しますか?"
      else
        current_user.reserve(@counseling_session)
        flash[:notice] = "セッションを予約しました"
      end
    end
  rescue RuntimeError
    flash[:error] = "このセッションは予約できません(#{$!})"
  end

ビューは、jascaffoldで作成したパーシャルビューを利用しました。

<h1><%= localize(:model, :counseling_session) %><%= localize(:title, :show) %></h1>

<div class="indent">
<%= render :partial=>"models/counseling_sessions/show2" %>

<br />

<%= start_form_tag %>
<%- case @counseling_session.status -%>
<%- when :free -%>
  <%= submit_tag '予約' %>
  <%= link_to 'キャンセル', :action => 'index' %>
<%- else -%>
  <%= link_to '一覧へ', :action => 'index' %>
<%- end -%>
<%= end_form_tag %>

</div>

画面イメージ

キャンセルは、予約をコピペして作りました。まずテスト

  def test_cancel_for_guest
    get :cancel, :id=>10001
    assert_redirected_to :controller=>'client', :action => 'login'
  end

  def test_cancel_confirm_for_client
    get :cancel, { :id=>10003 }, :user=>@cl1
    assert_match /reserved1/, @response.body
    assert_match /キャンセルしますか/, @response.body
  end

  def test_cancel_for_client
    post :cancel, { :id=>10003, :commit=>'キャンセルする' }, :user=>@cl1
    assert_match /reserved1/, @response.body
    assert_match /キャンセルしました/, @response.body

    assert assigns(:counseling_session)
    assert_equal assigns(:counseling_session), CounselingSession.find(10003)
    assert_equal :free, assigns(:counseling_session).status
    assert_equal nil, assigns(:counseling_session).client
  end

  def test_cancel_for_client_error
    assert_equal @cl1, CounselingSession.find(10003).client
    post :cancel, { :id=>10003, :commit=>'キャンセルする' }, :user=>@cl2
    assert_match /キャンセルできません/, @response.body

    assert assigns(:counseling_session)
    assert_equal :reserved, assigns(:counseling_session).status
    assert_equal @cl1, assigns(:counseling_session).client
    assert_equal @cl1, CounselingSession.find(10003).client
  end

実装。

  def cancel
    case current_user
    when nil, Counselor
      store_location
      flash[:notice] = "予約/キャンセルを行うにはログインが必要です"
      redirect_to :controller=>'client', :action => 'login'
    when Client
      @counseling_session = CounselingSession.find(params[:id])
      unless params[:commit]
        flash[:notice] = "このセッションをキャンセルしますか?"
      else
        current_user.cancel(@counseling_session)
        flash[:notice] = "セッションをキャンセルしました"
      end
    end
  rescue RuntimeError
    flash[:error] = "このセッションはキャンセルできません(#{$!})"
  end

ビューと画面イメージは省略します(後程まとめて公開します)。