学年・学科など基本的に固定なデータのモデルは、特にコントローラを作成しない。これらを一括でテストおよび実装を移植する。
学年(Gakunen)モデルの定義
学年のマイグレーションは以下のようになる。honka = true に対して、gakunen が 1〜5、honka = false に対して gakunen が 1〜2 を取り得る。パターンは 7 通り。gakunen, honka の別に sort_order という属性があり、これを使ってソートする。Rails 4.2 になってから、generator で timestamps に null: false が設定されるようになっていた。これに対する対策は後で述べる。
class CreateGakunens < ActiveRecord::Migration def change create_table :gakunens do |t| t.integer :gakunen, null: false t.integer :sort_order, null: false t.boolean :honka, null: false t.timestamps null: false end add_index :gakunens, :sort_order, unique:true add_index :gakunens, [ :honka, :gakunen ] end end
FactoryGirl 関係
spec/factories/gakunen.rb は特定の初期値がないので、中身を空のままにする。
FactoryGirl.define do factory :gakunen do end end
Factory は spec/support/create_factories/gakunen_factory.rb で作成する。引数は 1 〜 7 までの文字列(.中で to_i してるだけなので、数値でも可)を受け取り、対応するオブジェクトを作成する。一括して全部作成する gakunen_all_factories も用意する。これらの雛形は昨日の template により、モデルテストの下に書かれているので、そこからコピーしてくればよい(ファイルを作ってしまうと必要ないときに消すのが面倒なのでこのようにしている)。
# @param [Fixnum, String] n オブジェクトを一意に決定する数値(文字も可) # @return [Gakunen] Gakunen FactoryGirl オブジェクト def gakunen_factory(nstr) n = nstr.to_i FG.find_or_create(:gakunen, gakunen: (n - 1) % 5 +1, sort_order: n, honka: n <= 5) end # @return [Array<Campus>] Campus FactoryGirl オブジェクトの配列 def gakunen_all_factories (1..7).map { |n| gakunen_factory(n) } end
テストの記述
spec/models/gakunen_spec.rb は前回のヘルパメソッドを用いて以下の様に記述した。昨日のメソッドの使用時の説明になるので、部分ごとに説明する。common validation check 部は nil 不可の属性確認、削除ができないことの確認、一意性制約の確認である。
require 'rails_helper' RSpec.describe Gakunen, :type => :model do context "common validation check" do before do @target = gakunen_factory 1 end check_presence_validates %i( gakunen sort_order honka ) check_reject_destroy_validates check_unique_validates(%i( sort_order )) { gakunen_factory 2 } end
次にクラスメソッド、インスタンスメソッドのテストのために、7 個のデータをすべて作成しておく。
context 'after all gakunens are registered' do before do @gakunens = gakunen_all_factories end
最初にクラスメソッドをテストする。subject は Gakunen クラスになる。一つのメソッドで 3 つのメソッドのテストが実施されている。
context 'for Gakunen class' do subject { Gakunen } it 'should receive honka_gakunens, senkouka_gakunens and all_gakunens' do target_method_send_test subject, :honka_gakunens, nil, @gakunens[0..4], :senkouka_gakunens, nil, @gakunens[5..6], :all_gakunens, nil, @gakunens end end
次にインスタンスメソッドをテストする。subject は @gakunens となり、7 個のインスタンスに対してそれぞれメソッドを実施し、一括で 7 個分のテストを行う。ここでは、:full_name, :short_name, :honka?、:senkouka? の 4 つのメソッドを一括で実施しているため、28 個のテストが実施されている。
context 'for each Gakunen instances' do subject { @gakunens } it 'should receive full_name, short_name, honka?, senkouka?' do target_array_method_send_test subject, :full_name, nil, %w( 本科1年 本科2年 本科3年 本科4年 本科5年 専攻科1年 専攻科2年 ), :short_name, nil, %w( H1 H2 H3 H4 H5 S1 S2 ), :honka?, nil, [ true, true, true, true, true, false, false ], :senkouka?, nil, [ false, false, false, false, false, true, true ] end
同様に前の学年、次の学年を得るメソッドをテストする。
it 'should receive pre_gakunen and post_gakunen' do target_array_method_send_test subject, :pre_gakunen, nil, [ nil, @gakunens[0..3], nil, @gakunens[5] ].flatten, :post_gakunen, nil, [ @gakunens[1..4], nil, @gakunens[6], nil ].flatten end
最後に honka_ichinen? から senkouka_ninen? までのテストメソッドをテストする。
it 'should receive honka_*nen?, senkouka_*nen?' do target_array_method_send_test subject, :honka_ichinen?, nil, [ true, false, false, false, false, false, false ], :honka_ninen?, nil, [ false, true, false, false, false, false, false ], :honka_sannnen?, nil, [ false, false, true, false, false, false, false ], :honka_yonen?, nil, [ false, false, false, true, false, false, false ], :honka_gonen?, nil, [ false, false, false, false, true, false, false ], :senkouka_ichinen?, nil, [ false, false, false, false, false, true, false ], :senkouka_ninen?, nil, [ false, false, false, false, false, false, true ] end end end end
モデルの実装
app/models/gakunen.rb は以下のようになる。ほとんど以前作った Year と同様であるが、Rails 4.1 から導入された ActiveRecord enum を使ってみた。旧システムでは、honka_xxnen? 系のメソッドは honka と gakunen を使ってテストするメソッドを記述していたが、enum によりメソッドは自動生成される。enum については、別のモデルでより便利な使い方をしているので、その時に別途説明する。
# @!attribute year # @return [Fixnum] 学年 (1〜5) # @!attribute sort_order # @return [Fixnum] 並び順 (1〜7、専攻科は6, 7) # @!attribute honka # @return [Boolean] 本科であれば true class Gakunen < ActiveRecord::Base LoadKey = %i( id gakunen sort_order honka ) validates :gakunen, :sort_order, presence: true validates :honka, inclusion: { in: [ true, false ] } validates :sort_order, uniqueness: true GakunenHash = { honka_ichinen: 1, honka_ninen: 2, honka_sannnen: 3, honka_yonen: 4, honka_gonen: 5, senkouka_ichinen: 6, senkouka_ninen: 7 } enum sort_order: GakunenHash before_destroy :on_destroy_validation_method # @return [Boolean] false # @note 学年は削除できない def on_destroy_validation_method errors.add(:base, '学年は削除できません') false end private :on_destroy_validation_method # @return [Array<Gakunen>] gakunen が n の学年一覧を得る scope :gakunen_value_is, -> v { where arel_table[:gakunen].eq v } # @return [Array<Gakunen>] honka が flag の学年一覧を得る scope :honka_value_is, -> f { where arel_table[:honka].eq f } # @return [Array<Gakunen>] sort_order 順に並んだ学年一覧を得る scope :order_sort_order, -> { order arel_table[:sort_order] } # @return [Array<Gakunen>] 本科の学年一覧を得る def self.honka_gakunens honka_value_is(true).order_sort_order end # @return [Array<Gakunen>] 専攻科の学年一覧を得る def self.senkouka_gakunens honka_value_is(false).order_sort_order end # @return [Array<Gakunen>] 全学年一覧を得る def self.all_gakunens order_sort_order end # @return [String] '本科x年'または'専攻科x年'を返す def full_name "#{honka ? '本科' : '専攻科'}#{gakunen}年" end # @return [String] 'Hx'または'Sx'を返す def short_name "#{honka ? 'H' : 'S'}#{gakunen}" end # @return [Boolean] 専攻科なら true def senkouka? !honka end # @return [Gakunen] 学年と本科フラグから学年を得る def self.gakunen_from_gakunen_and_honka(g, h) self.gakunen_value_is(g).honka_value_is(h).first end # @return [Gakunen] 一つ下の学年を得る def pre_gakunen gakunen == 1 ? nil : Gakunen.gakunen_from_gakunen_and_honka(gakunen - 1, honka) end # @return [Gakunen] 一つ上の学年を得る def post_gakunen (honka_gonen? || senkouka_ninen?) ? nil : Gakunen.gakunen_from_gakunen_and_honka(gakunen + 1, honka) end end
timestamps の null 禁止の対応
Rails 4.2 では timestamps が null 禁止推奨となった。今後は完全に禁止になるとのこと。Rails で作成されたデータは当然これらは入っているので問題がないが、別のデータベースから流し込んだデータはこの限りではない。本システムは古いデータは 2001 年に作成されたものもあり、これらのデータについては null のまま運用していた。今後移行時に問題となるので、旧システムの方で対応しておく。
旧システムの方で、以下の簡易スクリプトを書いた。これを load し、新システムで実装完了するたびに、流し込みデータに関して timeCorrect メソッドを実行し、TimeStamp を初期値に設定する。物によってはデータ量も多いので、%表記もしているので、ちゃんと動いていることが確認できる。
def timeCorrect(c) d = DateTime.new(2009, 4, 1) cc = c.where(created_at:nil) ccn = cc.count pre = -1 cc.each_with_index do |o, i| o.created_at = d o.save n = (i+1)*100/ccn unless pre == n print "#{i+1}/#{ccn} (#{n}%)\n" pre = n end end cu = c.where(updated_at:nil) cun = cu.count cu.each_with_index do |o, i| o.updated_at = d o.save n = (i+1)*100/cun unless pre == n print "#{i+1}/#{cun} (#{n}%)\n" pre = n end end ccn + cun end
今日はここまで。
written by iHatenaSync