Order#target と Order#target=
理想は TDD なんだけど、実際に処理を書いてみないと挙動のイメージがつかめないことが多いので、どうしてもある程度コードが形になってからテストを用意するスタイルになってしまう。
安定してきたらテスト先行に移れると思うけど当面はやむなしか。
実装は Order だけど、実際に使用されるのは SupportOrder か ConvoyOrder なので、SupportOrder のテストとして書いた。ConvoyOrder のテストは書くとしても差分が必要な場合だけになる予定。
spec/models/support_order_spec.rb
初めて shared_context と let を活用してみたので、いろいろ実験した結果長くなった。試しに全文貼ってみる。
正直 SupportOrder#new で生成した場合の挙動や target 属性未指定で生成した場合の挙動はテストする必要があったのか疑問だけど、「どうなるか分からない」で放置するのも怖かったので書くだけ書いておいた。
行軍解決処理のテストはこの何倍も長くなるんだろうなあ。
require 'spec_helper'
describe SupportOrder do
let(:hold_order) { FactoryGirl.create :hold_order }
let(:move_order) { FactoryGirl.create :move_order }
shared_context "SupportOrder#new で生成", support_order_gen: :new do
let(:support_order) { FactoryGirl.build :support_order, target: target }
end
shared_context "SupportOrder#create で生成", support_order_gen: :create do
let(:support_order) { FactoryGirl.create :support_order, target: target }
end
shared_context "target が nil の場合", target: :nil do
let(:target) { nil }
end
shared_context "target が HoldOrder の場合", target: :hold do
let(:target) { hold_order }
end
shared_context "target が MoveOrder の場合", target: :move do
let(:target) { move_order }
end
describe "#new", support_order_gen: :new do
context "target が nil の場合", target: :nil do
subject { support_order.target }
example { expect(subject).to be_nil }
end
context "target が HoldOrder の場合", target: :hold do
subject { support_order.target }
example { expect(subject).not_to be_nil }
end
context "target が MoveOrder の場合", target: :move do
subject { support_order.target }
example { expect(subject).not_to be_nil }
end
end
describe "#create", support_order_gen: :create do
context "target が nil の場合", target: :nil do
subject { support_order.target }
example { expect(subject).to be_nil }
end
context "target が HoldOrder の場合", target: :hold do
subject { support_order.target }
example { expect(subject).not_to be_nil }
end
context "target が MoveOrder の場合", target: :move do
subject { support_order.target }
example { expect(subject).not_to be_nil }
end
end
describe "#target", support_order_gen: :new do
context "target が HoldOrder の場合", target: :hold do
subject { support_order.target }
example { expect(subject).not_to eq hold_order }
example { expect(subject.id).to be_nil }
example { expect(subject.sample).to be_true }
example { expect(subject.unit).to eq hold_order.unit }
example { expect(subject.province).to eq hold_order.province }
example { expect(subject.persisted?).to be_false }
end
context "target が MoveOrder の場合", target: :move do
subject { support_order.target }
example { expect(subject).not_to eq move_order }
example { expect(subject.id).to be_nil }
example { expect(subject.sample).to be_true }
example { expect(subject.unit).to eq move_order.unit }
example { expect(subject.src).to eq move_order.src }
example { expect(subject.dst).to eq move_order.dst }
example { expect(subject.persisted?).to be_false }
end
end
describe "#target", support_order_gen: :create do
context "target が HoldOrder の場合", target: :hold do
subject { support_order.target }
example { expect(subject).not_to eq hold_order }
example { expect(subject.id).not_to be_nil }
example { expect(subject.sample).to be_true }
example { expect(subject.unit).to eq hold_order.unit }
example { expect(subject.province).to eq hold_order.province }
example { expect(subject.persisted?).to be_true }
end
context "target が MoveOrder の場合", target: :move do
subject { support_order.target }
example { expect(subject).not_to eq move_order }
example { expect(subject.id).not_to be_nil }
example { expect(subject.sample).to be_true }
example { expect(subject.unit).to eq move_order.unit }
example { expect(subject.src).to eq move_order.src }
example { expect(subject.dst).to eq move_order.dst }
example { expect(subject.persisted?).to be_true }
end
end
describe "#target=", support_order_gen: :new do
context "後から設定", target: :nil do
before do
support_order.target = hold_order
end
subject { support_order.target }
example { expect(subject).not_to be_nil }
example { expect(subject).not_to eq hold_order }
example { expect(subject.unit).to eq hold_order.unit }
example { expect(subject.province).to eq hold_order.province }
example { expect(subject.persisted?).to be_false }
end
context "書き換え", target: :hold do
before do
@lt = support_order.target
support_order.target = move_order
end
subject { support_order.target }
example { expect(subject).not_to be_nil }
example { expect(subject).not_to eq move_order }
example { expect(subject.unit).to eq move_order.unit }
example { expect(subject.src).to eq move_order.src }
example { expect(subject.dst).to eq move_order.dst }
example { expect(subject.persisted?).to be_false }
example { expect(@lt.persisted?).to be_false }
end
end
describe "#target=", support_order_gen: :create do
context "後から設定", target: :nil do
before do
support_order.target = hold_order
end
subject { support_order.target }
example { expect(subject).not_to be_nil }
example { expect(subject).not_to eq hold_order }
example { expect(subject.unit).to eq hold_order.unit }
example { expect(subject.province).to eq hold_order.province }
example { expect(subject.persisted?).to be_true }
end
context "書き換え", target: :hold do
before do
@lt = support_order.target
support_order.target = move_order
end
subject { support_order.target }
example { expect(subject).not_to be_nil }
example { expect(subject).not_to eq move_order }
example { expect(subject.unit).to eq move_order.unit }
example { expect(subject.src).to eq move_order.src }
example { expect(subject.dst).to eq move_order.dst }
example { expect(subject.persisted?).to be_true }
example { expect(@lt.persisted?).to be_false }
end
end
end
Order#target= リファクタリング
少しだけ綺麗になった。
class Order < ActiveRecord::Base
alias_method :association_target=, :target=
def target=(target)
if target
sample = target.dup
sample.sample = true
else
sample = nil
end
self.target.destroy if self.target
self.association_target = sample
save! if persisted?
end
end
belongs_to 宣言によって設定された Order#target= メソッドは Order.create(および Order.new)の実行時にも呼ばれる。
引数として nil と Order の派生クラスのオブジェクト以外が渡された場合の動きは考慮していないが、その場合は sample.sample = true で NoMethodError 例外が飛ぶ。
万が一 sample= メソッドを持つオブジェクトでも self.assosiation_target = sample で ActiveRecord::AssociationTypeMismatch 例外になるので検出漏れはない。
例外が飛ぶ時点で呼び出し側のバグは明白なので、Order#target= としてはこれ以上の配慮は不要だろう。