カフェでプログラミング

http://d.hatena.ne.jp/h_gomi/20071117/1195305270
で書いたカフェプログラミングの続きを友達と一緒にやりました。


今日は家の近くのカフェで。
(http://d.hatena.ne.jp/h_gomi/20071118/1195395227
で行ってバナナマフィン食べたところ)
ランチでパスタ食べて、3時ごろにバナナマフィン食べました。
1150円で3時間半粘っておいしくておなかいっぱいで、
ネット使えるので満足です。家から徒歩1分というのもすばらしい!


さて本日は状態遷移テストに取り組みました。
簡単に動くコード書いて、
DRYに変えて、テスト作って。
テスト駆動までは行ってないけど、プログラミングとして
いい感じに進みました。


今日作ったコードの一部を公開しようと思いますが、
その前に本日のTipsを。

  • コントローラのテストでは、セッションへのsetが必要
  • エディタの文字コードはテスト前に要チェック。特に日本語使う場合。
  • コマンドプロンプト文字コードを変える(utf-8):chcp 65001
  • HTTPのステータスが謎。テストにて、未ログイン⇒302、ログイン済⇒success(200)
  • javascript、要素をidで取得 f.issue_status.value='#{status}';
  • DRY

課題管理アプリなので中心となるモデルはIssueくん。

class Issue < ActiveRecord::Base
  
    HUMANIZED_ATTRIBUTE_KEY_NAMES = {'Title' => 'タイトル', 'Category' => 'カテゴリ', 'Priority' => '優先度', 'Description' => '説明', 'Next check date' =>'次回チェック日', 'Status' => 'ステータス'}

    attr_accessor :db_status


  def self.japanese_col_name english_col_name
    HUMANIZED_ATTRIBUTE_KEY_NAMES[english_col_name]
  end

  validates_length_of   :title, :maximum=>100, :message =>'は100桁以内で入力してください'
  validates_presence_of :title, :message =>'は必須です'
  validates_format_of :title, :message =>'は必須です。記号はハイフン「-」 アンダーバー「_」のみ使えます', :with => /^[\wa-zA-Z0-9\-_()\\!?]*$/


  def self.statuses
    ["未着手", "対応中", "回答待", "リリース待", "完了"]
  end
  
  def next_statuses
    case db_status
    when '未着手'
        ['未着手','回答待','対応中','完了']
    when '回答待'
        ['回答待','対応中']
    when '対応中'
        ['回答待','対応中','リリース待']
    when 'リリース待'
        ['リリース待','完了']
    when '完了'
        ['未着手','回答待','対応中','リリース待','完了']
    else
        ['未着手','回答待','対応中','リリース待','完了']
    end
  end

end


そしてIssueくんのテスト

require File.dirname(__FILE__) + '/../test_helper'

class IssueTest < Test::Unit::TestCase
  fixtures :issues

  
  def test_validation_category
    issue = Issue.new(:category => '適当なカテゴリ', :priority=>'適当',:description=>'適当',:next_check_date=>Time.now,:status=>'てきとー')



    # タイトルが101文字の場合
    issue.title = "あああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああ"
    assert !issue.valid?

    # タイトルが100文字の場合
    issue.title = "ああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああ"
    assert issue.valid?

    # タイトルが1文字の場合
    issue.title = ""
    assert issue.valid?

    # タイトルが0文字の場合
    issue.title = ""
    assert !issue.valid?
    
    # タイトルが半角スペースのみの場合
    issue.title = " "
    assert !issue.valid?
    
    # タイトルが全角スペースのみの場合
    issue.title = " "
    assert issue.valid?
    
    # タイトルが半角スペースのみの場合
    issue.title = "     "
    assert !issue.valid?

    # タイトルがタブの場合
    issue.title = "\t"
    assert !issue.valid?

    # タイトルがタブの場合
    issue.title = ' '
    assert !issue.valid?
    assert_equal issue.errors[:title].first, 'は必須です。記号はハイフン「-」 アンダーバー「_」のみ使えます'
  end

  def test_next_status

    # 新規作成の場合 
    issue = Issue.new(:status => nil)
    assert_equal issue.next_statuses,['未着手','回答待','対応中','リリース待','完了']

    # 既存ステータスが未着手の場合 
    issue = Issue.new(:db_status => '未着手')
    assert_equal issue.next_statuses,['未着手','回答待','対応中','完了']

    # 既存ステータスが回答待の場合 
    issue = Issue.new(:db_status => '回答待')
    assert_equal issue.next_statuses,['回答待','対応中']

    # 既存ステータスが対応中の場合 
    issue = Issue.new(:db_status => '対応中')
    assert_equal issue.next_statuses,['回答待','対応中','リリース待']

    # 既存ステータスがリリース待の場合 
    issue = Issue.new(:db_status => 'リリース待')
    assert_equal issue.next_statuses,['リリース待','完了']

    # 既存ステータスが完了の場合 
    issue = Issue.new(:db_status => '完了')
    assert_equal issue.next_statuses,['未着手','回答待','対応中','リリース待','完了']

  end

end


あとはコントローラとそのテスト

require 'hikidoc'
class KanriController < ApplicationController
  def index
    list
    render :action => 'list'
  end

  # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html)
  verify :method => :post, :only => [ :destroy,:create, :update ],  
         :redirect_to => { :action => :list }

  def list
   @check_date = DateTime.now
   @check_date = DateTime.new(params[:date][:year].to_i, params[:date][:month].to_i,params[:date][:day].to_i,params[:date][:hour].to_i,params[:date][:minute].to_i)  if params[:date]
   @issue_pages, @issues = paginate(:issues, 
                                 :conditions => ["next_check_date <= ?", @check_date],
                                 :per_page => 10)
  end

  def show
    @issue = Issue.find(params[:id])
  end

  def new
    if params[:issue]
      @issue = Issue.new(params[:issue])
    else
      @issue = Issue.new
    end
  end

  def create
    @issue = Issue.new(params[:issue])
    if @issue.save
      flash[:notice] = '課題は正常に登録されました。'
      redirect_to :action => 'list'
    else
      render :action => 'new'
    end
  end

  def edit

    if params[:issue]
      @issue = Issue.new(params[:issue])
    else
      @issue = Issue.find(params[:id])
      @issue.db_status = @issue.status
    end
  end

  def update
    @issue = Issue.find(params[:id])
    if @issue.update_attributes(params[:issue])
      flash[:notice] = '課題は正常に更新されました。'
      redirect_to :action => 'show', :id => @issue
    else
      render :action => 'edit'
    end
  end

  def destroy
    Issue.find(params[:id]).destroy
    redirect_to :action => 'list'
  end
end
require File.dirname(__FILE__) + '/../test_helper'
require 'kanri_controller'

# Re-raise errors caught by the controller.
class KanriController; def rescue_action(e) raise e end; end

class KanriControllerTest < Test::Unit::TestCase
  fixtures :issues

  def setup
    @controller = KanriController.new
    @request    = ActionController::TestRequest.new
    @response   = ActionController::TestResponse.new
    @request.session[:login_user] = {:uid => 'gomi'}
  end


  def test_new
    get :new
    assert_response :success
    assert_template 'new'
    assert_not_nil assigns['issue']

    assigns(:issue).title = 'aa'
    
    assert_equal assigns(:issue).next_statuses, ['未着手','回答待','対応中','リリース待','完了']
    assigns(:issue).next_statuses.each do |next_status|
      assigns(:issue).status = next_status
      post :create , :issue => {:title => assigns(:issue).title, :status => next_status, :next_check_date=>Time.now}
      assert_response :redirect
      assert_redirected_to :action => 'list'
    end 
  end


  def test_edit
    #get :edit, :id => @first_id
    assert true

  end

end

ビューその1 new.rhtml

<h2>課題の登録</h2>

<% form_tag :action => 'create' do %>
  <%= render :partial => 'form' %>
  <%= submit_tag "作成" %>
<% end %>

<%= link_to '戻る', :action => 'list' %>

ビューその2 _form.rhtml

<%= error_messages_for 'issue' %>
<% @issue.next_statuses.each do |status|%>
<%=link_to_status status%>
<% end %>
<!--[form:issue]-->
<p><label for="issue_title">タイトル</label><br/>
<%= text_field 'issue', 'title'  %></p>

<p><label for="issue_category">カテゴリ</label><br/>
<%= text_field 'issue', 'category'  %></p>

<p><label for="issue_priority">優先度</label><br/>
<%= text_field 'issue', 'priority'  %></p>

<p><label for="issue_description">説明</label><br/>
<%= text_area 'issue', 'description'  %></p>

<p><label for="issue_next_check_date">次回チェック日</label><br/>
<%= datetime_select 'issue', 'next_check_date'  %></p>

<p><label for="issue_status">ステータス</label><br/>
<%= select :issue, :status, Issue.statuses  -%><br/></p>

<%=hidden_field 'issue','id' %>
<%=hidden_field 'issue','db_status' %>
<!--[eoform:issue]-->

最後にヘルパー

module KanriHelper
  def link_to_status(status)
    link_to_function(status,"var f = document.forms[0] ;f.action = '#{@issue.id}'; f.issue_status.value='#{status}'; f.method = 'POST';f.submit()")
  end
end

※テスト自体の勉強という意味もあるので、
普通そういう書き方しないでしょというのもあるし、
一部だけ試してみて、網羅性までは確認できてないとうのもあります


今回は、ある課題を初期作成したり、
編集したりするときに、次のどのステータスに変われるのかの例をだし、
そのステータスごとに設定のお助け機能をつけようというもの。
まだお助け機能はほとんどできていないですが、今のステータスを判別して、
次にどのステータスに変われるのかはうまく動いているようです。


次は年明けに集まる予定。
状態遷移のテストを完成させて、次のステップに行きたいです。