複数形の登録
キャンパスのモデルである。これも同様であるが、一つだけハマリポイントがあった。generator で campus モデルを作成しようとしたら、campu の複数形と判断されてしまった。旧システムの情報を参考に config/initializers/inflections.rb に campus の複数形である campuses を登録した。あとで作成する syllabus の複数形である syllabuses も同時に登録しておいた。システムで数回しかやらない設定はどうしても忘れてしまう。
ActiveSupport::Inflector.inflections do |inflect| inflect.irregular("campus", "campuses") inflect.irregular("syllabus", "syllabuses") end
この設定をして generator も動作した。
マイグレーションと FactoryGirl の設定
spec/support/create_factories/campus_factory.rb は以下のようにした。引数は sort_order の enum キーになっており、Campus.sort_orders hash を引くことで実際の値(1〜3)になる。
CampusFactoryHash = { shinagawa: %w( 品川 S ), arakawa: %w( 荒川 A ), both: %w( 両 B ) } # @param [Symbol, String] key オブジェクトを一意に決定するキー # @return [Campus] Campus FactoryGirl オブジェクト def campus_factory(key) keysym = key.to_sym n, c = CampusFactoryHash[keysym] FG.find_or_create( :campus, name: n, code: c, sort_order: Campus.sort_orders[keysym]) if n end # @return [Array<Campus>] Campus FactoryGirl オブジェクトの配列 def campus_all_factories CampusFactoryHash.keys.map { |k| campus_factory(k) } end
モデルのテスト
model spec は以下の通り。Gakunen と同様、nil 不可の確認、削除不可の確認、一意性の確認をする。
require 'rails_helper' RSpec.describe Campus, :type => :model do context "common validation check" do before do @target = campus_factory :shinagawa end check_presence_validates %i( name code sort_order ) check_reject_destroy_validates check_unique_validates(%i( code sort_order )) { campus_factory :arakawa } end
次にクラスメソッド。subject は Campus。campuses は品川と荒川だけを取得する。with_both をつけた時だけ両キャンパスも得る。
context 'after all campuses are registrered' do before do @campuses = campus_all_factories end context 'for Campus class' do subject { Campus } it 'should receive campuses, campuses_with_both' do target_method_send_test subject, :campuses, nil, @campuses[0..1], :campuses_with_both, nil, @campuses end
キャンパスが含まれるキャンパス一覧を得る campus_included(c) をテストする。品川の場合には、品川と両、荒川の場合には荒川と両が得られることを期待する。default(ip) は ip アドレスからキャンパスを得るメソッドである。テストの時には品川になることを期待する。
it 'should receive campuses_included, default' do s, a, b = @campuses target_method_send_test subject, :campuses_included, s, [ s, b ], :campuses_included, a, [ a, b ], :default, '荒川のIPその1', a, :default, '荒川のIPその1'', a, :default, '127.0.0.1', s, :default, '品川のIPその1'', s end
クラスメソッド最後は、get_arakawa、get_shinagawa、get_both でそれぞれのキャンパスが得られることを期待する。以前は、arakawa, shinagawa, both というメソッドであったが、enum によりこれらのメソッドが定義されているので、名前を変えた。
it 'should receive get_arakawa, get_shinagawa, get_both' do s, a, b = @campuses target_method_send_test subject, :get_shinagawa, nil, s, :get_arakawa, nil, a, :get_both, nil, b end end
次はインスタンスメソッド。subject は @campuses。まず、shinagawa?、arakawa?、both? でキャンパスを診断できることを期待する。
context 'for Campus instances' do subject { @campuses } it 'should receive arakawa? shinagawa? both?' do target_array_method_send_test subject, :shinagawa?, nil, [ true, false, false ], :arakawa?, nil, [ false, true, false ], :both?, nil, [ false, false, true ] end
include_XXX? で各キャンパスを含むかどうかを調べられることを期待する。
it 'should receive include_arakawa? include_shinagawa?' do target_array_method_send_test subject, :include_shinagawa?, nil, [ true, false, true ], :include_arakawa?, nil, [ false, true, true ] end
ename で英語名、name_with_campus で XX キャンパスという文字列が得られることを期待する。
it 'should recieve ename, name_with_campus' do target_array_method_send_test subject, :ename, nil, %w( shinagawa arakawa both ), :name_with_campus, nil, %w( 品川キャンパス 荒川キャンパス 両キャンパス ) end
include_camplus?(campus)で campus が含まれることを期待する。これで取り急ぎのテストは終了。
it 'should receive is_campus?' do s, a, b = @campuses target_array_method_send_test subject, :include_campus?, s, [ true, false, true ], :include_campus?, a, [ false, true, true ], :include_campus?, b, [ false, false, true ] end end end end
モデルの実装
上記のテストが通過するモデルを実装する。基本的には今まで通りなので、enum に関わる注意すべきところだけ記述する。
enum は sort_order に設定する。この記述により、shinagawa?、arakawa?、both? は実装することなくテストが通過する。
enum sort_order: { shinagawa: 1, arakawa: 2, both: 3 }
enum に設定した key に対応する値は Campus.sort_orders[key] で取得する。これを使って scope を設定する。
# @return [Array<Campus>] sort_order 順に並んだキャンパス一覧を得る scope :order_sort_order, -> { order arel_table[:sort_order] } # @return [Array<Campus>] 両キャンパスを除いたキャンパス一覧を得る scope :without_both, -> { where arel_table[:sort_order].not_eq(Campus.sort_orders[:both]) } # @return [Array<Campus>] 両キャンパスを除いた整列済キャンパス一覧を得る scope :campuses, -> { without_both.order_sort_order } # @return [Array<Campus>] 両キャンパスを含んだ整列済キャンパス一覧を得る scope :campuses_with_both, -> { order_sort_order } # @return [Array<Campus>] キャンパスを含むキャンパス一覧を得る(必ず両が含まれる) scope :campuses_included, -> c { where arel_table[:sort_order].in([ Campus.sort_orders[c.sort_order], Campus.sort_orders[:both]]) }
Campus.shinagawa とすると、sort_order の値が :shinagawa であるものの配列が得られる。今回は一つの値しかないので、take で一つ取得する。他も同様である。
# @return [Campus] 品川キャンパス def self.get_shinagawa shinagawa.take end # @return [Campus] 荒川キャンパス def self.get_arakawa arakawa.take end # @return [Campus] 両キャンパス def self.get_both both.take end
include_shinagawa? は品川か両であれば true になる。enum により shinagawa? || both? と書くだけで済んでしまう。include_arakawa?、include_campus?(campus) も同様である。
# @return [Boolean] 品川キャンパスであれば true (両キャンパスも true) def include_shinagawa? shinagawa? || both? end # @return [Boolean] 荒川キャンパスであれば true (両キャンパスも true) def include_arakawa? arakawa? || both? end # @return [Boolean] キャンパスが c を含むか? def include_campus?(c) sort_order == c.sort_order || both? end end
sort_order はデータベース内では数値だが、ActiveRecord 内では Symbol になる。そのため、ename は sort_order を返せばよい。
# @return [String] 英語名 def ename sort_order end
今日はここまで。
written by iHatenaSync