Quantcast
Channel: Ruby on Railsの記事一覧|TechRacho by BPS株式会社
Viewing all articles
Browse latest Browse all 1406

FactoryGirlでhas_manyな関係を定義する方法

$
0
0

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に関連する状態になりました
ファクトリーの定義だけでこれと同じことが出来ると良いのですが、やり方が見当たらなかったのでこうなっています
ファクトリーの定義だけで完結する書き方を知っていたら教えて下さい


Viewing all articles
Browse latest Browse all 1406

Trending Articles