注: factory_girlはfactory_botに改名されました。
FactoryGirlでhas_manyな関連を定義する方法が、ドキュメントをみただけでは分かりにくかったのでメモを残します
gemのVersionはそれぞれ
- Ruby: 1.9.3-p392
- Rails: 3.2.13
- FactoryGirl: 4.2.0
サンプルとして科目(Subject)が複数の単元(Unit)を持っているというデータを作成します
Model
# app/models/subject.rb
class Subject < ActiveRecord::Base
has_many :units
validates_uniquness_of :code
attr_accessible :code, :name
end
# app/models/unit.rb
class Unit < ActiveRecord::Base
belongs_to :subject
validates_uniquness_of :code
attr_accessible :code, :name
end
Subject、Unitともに codeとnameを持っていて、codeの重複は禁止
ファクトリーの定義を作る
ファクトリーの定義は spec/factories.rb というファイルに記述します
- spec/factories.rb
# coding: utf-8
FactoryGirl.define do
factory :english, class: Subject do
code "subject_01"
name "英語"
end
factory :grammar, class: Unit do
association :subject, factory: :english
code "unit_01"
name "文法"
end
factory :idiom, class: Unit do
association :subject, factory: :english
code "unit_02"
name "熟語"
end
end
- spec/model/sample_spec.rb
describe 'sample' do
it 'Subject:英語 が作成できる' do
english = FactoryGirl.create(:english) # 無事作成されました
english.should_not be_nil # 結果はgreenです
end
it 'Unit:文法、熟語 が作成できる' do
FactoryGirl.create(:grammar) # 無事作成されました
FactoryGirl.create(:idiom) # uniquness制約によるエラーが出ました
Unit.all.should have(2).items # 「熟語」にあたるUnitが作成されなかったので結果はredになります
end
end
うまくいきませんね
Subjectのcodeが重複してしまいます
今の定義だとUnitを作成する時点で毎回Subjectを作成しようとするようです
Subjectを複数回作成してしまうと、code(subject_01)が重複するのでエラーになりますね
解決方法
重複を回避するには、Sequencesという機能が使えます
これは自動的に連番を生成してくれる機能なので
code1 code2 code3... と重複しないcodeを設定できます
Subjectの定義を変更する
factory :english, class: Subject do
code sequence(:code) {|n| "subject_#{n}" }
name "英語"
end
今度は成功しました
しかし、今の状態ではUnitがそれぞれ別のSubjectを持ってしまっています
SubjectとUnitが1対1(has_one) という関係に近い状態ですね
一つのSubjectが複数のUnitを持っているという状態(has_many)を実現できていません
テストコード側の対策
同じSubjectを参照するようにするためには、テストコード側で対策が必要です
FactoryGirlは定義されたattributesをoverride する機能があるので、
Unit作成時にSubjectのインスタンスを渡すことで同一のSubjectをセットできます
it 'Unit:文法、熟語 が作成できる' do
@english = FactoryGirl.create(:english)
FactoryGirl.create(:grammar, subject: @english)
FactoryGirl.create(:idiom, subject: @english)
Unit.all.should have(2).items
end
これで、文法と熟語が同じSubjectに関連する状態になりました
ファクトリーの定義だけでこれと同じことが出来ると良いのですが、やり方が見当たらなかったのでこうなっています
ファクトリーの定義だけで完結する書き方を知っていたら教えて下さい