20. Year モデルの和暦表示

前書き

旧システム作成時には view から controller のメソッド呼び出しを知らなかったので、ページ呼び出し時にセッションに関わる情報を一括してインスタンス変数に代入していた。このページもそれを踏襲して、現在の年度を @now_year に代入するという手順で記述していた。後日の修正で、helper_method への対応をしてしまったので、このページも後で修正したものに記述を変更してみる。そのため、若干テスト結果の違いなどがあるかもしれないが、ご了承いただきたい。

受入テストの記述

権限関係が落ち着いたので、ペンディングにしていた年度変更の受入テストを再開する。spec/acceptance/features/kyoumu_years.feature を次のように変更する。

# language: ja
機能: 年度の追加、変更
  シナリオ: 教務室のトップページから年度変更に飛び、新規年度を作成し、年度を変更
する
    前提 教務室でログインする
    ならば "平成26年度・"と表示されていること
    もし "[年度変更]"リンクをクリックする
    ならば 年度一覧と表示されていること
    もし "[平成27年度を作成]”ボタンをクリックする
    ならば "[平成27年度]"と表示されていること
    もし "[平成27年度]"リンクをクリックする
    ならば "平成27年度・"と表示されていること

現在、”平成26年度・”は直書きしているため最初のテストは通過している。この部分を可変にするように app/views/shared/_top_link_bar.html.haml の該当部分を以下のように変更する。

    %li= "#{now_year.wareki_nendo}・ユーザ名"

now_year は controller のセッション情報からデータを取得するものである。このため、メソッドは controller に記述する。

コントローラの設定

now_year は app/controllers/application_controller.rb に作成する。このメソッドは以下の順序で現在の年度を返却することとする。すなわち、controller の責任で呼び出せればそれを使い、できなければ残りは model に依頼する。
# コントローラ内のキャッシュである @now_year
# user_session または admin_session に保存されている now_year_id から取得した年度
# Year の中で default_year に設定されている年度(この時、now_year_id をセッションに保存する)
# Year に何も登録されていなければ平成26年度をデフォルト年度として作成したもの(こちらも now_year_id をセッションに保存する)
この実装は以下の通り。

  # @return [Year] セッションから取得した年度もしくはデフォルト年度
  def now_year        
    @now_year ||= Year.default(each_session(‘now_year_id’))
  end
  helper_method :now_year

Year model のテスト

Year.default がないと言われるので、spec/models/year_spec.rb にこのテストを作成する。最後のテストは Year に対するスタブを設定してみた。モックやスタブの書き方も以前と変わってきている。以前は、スタブは .stub のような表現だったが、allow や double を用いるように変わってきているようだ。今回は Year というモデルに引数付の create が呼ばれたら year を返すフリをさせるというものである。

  context "for Year.default(year_id)" do
    context "when Year.find(year_id) exists" do
      it "Year.default(existing_year.id) should eq esisting_year" do
        year.save
        expect(Year.default(year.id)).to eq(year)
      end
    end

    context "when year_id is nil and default_year exists" do
      it "Year.default(nil) should eq default_year" do
        year.save
        expect(Year.default(nil)).to eq(year)
      end
    end

    context "when year_id is nil and default_year does not exists" do
      it "Year.default(nil) should eq created year" do
        allow(Year).to receive(:create).with(year: 2014, default_year: true).and_return(year)
        expect(Year.default(nil)).to eq(year)
      end             
    end               
  end

このテストを通過させる実装は以下のようになる。

  # @param [Fixnum,nil] yid 年度ID
  # @return [Year] 1. year_id に対応する year が存在したらその年度
  # @return [Year] 2. default_year が true の year が存在したらその年度
  # @return [Year] 3. default_year が true の year を作成してその年度
  def self.default(yid)              
    if yid                           
      ans = Year.find(yid)                         
    else
      unless ans = Year.default_year_value_is(true).first
        ans = Year.create(year: 2014, default_year: true)
      end
    end
    ans           
  end

これで、Year が取得されて以下のようなエラーになった。

     Failure/Error: 前提 教員でログインする
     ActionView::Template::Error:
       undefined method `wareki_nendo' for #<Year:0x007fbb6e18d130>

Year model のクラスメソッドのテスト追加

あとは、Year に wareki_nendo を実装するだけである。まず、Year に関する関連するクラスメソッドのテストを spec/models/year_spec.rb に記述する。wareki_nendo だけでなく、wareki, ewareki, wareki_full, wareki_full_from_date などの関連メソッドも一気に作成する。

  context "for class methods of Japanese imperial year" do
    it "wareki, wareki_nendo, ewareki and wareki_num should have correct values" do
      answers = [
        [ [ 2014, 4, 1 ], "平成26", "H26", 26, "4月1日" ],
        [ [ 1989, 12, 31 ], "平成元", "H1", 1, "12月31日" ],
        [ [ 1989, 1, 8 ],  "平成元", "H1", 1, "1月8日" ],
        [ [ 1989, 1, 7 ],  "昭和64", "S64", 64, "1月7日" ],
      ]
      answers.each do |ymd, jy, ey, y, jmd|
        ymds = ymd.join(', ')
        expect(Year.wareki(*ymd)).to eq(jy)
        expect(Year.ewareki(*ymd)).to eq(ey)
        expect(Year.wareki_num(*ymd)).to eq(y)
        jymd = "#{jy}#{jmd}"
        expect(Year.wareki_full(*ymd)).to eq(jymd)
        expect(Year.wareki_full_from_date(Date.new(*ymd))).to eq(jymd)
        unless ymd[1] == 1
          yy = ymd[0]
          expect(Year.wareki(yy)).to eq(jy)
          expect(Year.wareki_nendo(yy)).to eq("#{jy}年度")
          expect(Year.ewareki(yy)).to eq(ey)
        end
      end
    end
  end

Year model のクラスメソッド実装

これらのメソッドに対するクラスメソッドを実装する。

  # @param [Fixnum] y 西暦
  # @param [Fixnum] m 月
  # @param [Fixnum] d 日 
  # @return [String] 和暦年
  def self.wareki(y, m = 12, d = 31)
    str, y = wareki_sub(y, m, d)
    "#{ { 'H' => '平成', 'S' => '昭和' }[str] }#{ y == 1 ? '' : y }"
  end 
      
  # @param [Fixnum] y 西暦
  # @param [Fixnum] m 月
  # @param [Fixnum] d 日
  # @return [Array<String, Fixnum>] 和暦記号, 年度
  def self.wareki_sub(y, m, d)
    return [ 'H', y - 1988 ] if y > 1989
    return [ 'H', 1 ] if y == 1989 && m > 1
    return [ 'H', 1 ] if y == 1989 && m == 1 && d > 7
    return [ 'S', y - 1925 ]
  end   
      
  # @param [Fixnum] y 西暦
  # @param [Fixnum] m 月
  # @param [Fixnum] d 日
  # @return [String] 和暦年(英語版)
  def self.ewareki(y, m = 12, d = 31)
    wareki_sub(y, m, d).join('')
  end
  
  # @param [Fixnum] y 西暦
  # @param [Fixnum] m 月
  # @param [Fixnum] d 日
  # @return [Fixnum] 和暦年(年のみ)
  def self.wareki_num(y, m = 12, d = 31)
    wareki_sub(y, m, d)[1]
  end

  # @param [Fixnum] y 西暦
  # @param [Fixnum] m 月
  # @param [Fixnum] d 日
  # @return [String] 和暦年(年度を含む)
  def self.wareki_nendo(y, m = 12, d = 31)
    "#{self.wareki(y, m, d)}年度"
  end

  # @param [Fixnum] y 西暦
  # @param [Fixnum] m 月
  # @param [Fixnum] d 日
  # @return [String] 和暦年月日
  def self.wareki_full(y, m, d)
    "#{self.wareki(y, m, d)}#{m}#{d}"
  end
    
  # @param [Date] date 日付
  # @return [String] 和暦年月日
  def self.wareki_full_from_date(date)
    self.wareki_full(date.year, date.month, date.day)
  end

Year モデルのインスタンスメソッドのテスト追加

同様に Year のインスタンスメソッドを作成する。テストとして以下を用意した。

  context 'for instance methods of Japanese imperial year' do
    it "wareki, wareki_nendo, ewareki and wareki_num should have correct values" do
      expect(year.wareki).to eq("平成26")
      expect(year.wareki_nendo).to eq("平成26年度")
      expect(year.ewareki).to eq("H26")
      expect(year.wareki_num).to eq(26)
    end
  end

Year model のインスタンスメソッド実装

このテストを通過する実装を作成する。

  # @return [String] 和暦年
  def wareki
    self.class.wareki(year)
  end
     
  # @return [String] 和暦年(英語版)
  def ewareki 
    self.class.ewareki(year)
  end         
     
  # @return [String] 和暦年(数値のみ)
  def wareki_num
    self.class.wareki_num(year)
  end      
           
  # @return [String] 和暦年(年度を含む)
  def wareki_nendo
    self.class.wareki_nendo(year)
  end

これでモデルのテストは通過する。長くなったので今日はここまで。

written by iHatenaSync