読者です 読者をやめる 読者になる 読者になる

こしごぇ(B)

旧:http://d.hatena.ne.jp/koshigoeb/

FactoryGirl を使って冪等な Database seeding をやってみた

development, test 以外で FactoryGirl を使うのどうなんだ、という話は全力で脇に置く。こういう誤魔化し方もあるのかな、という試み。

んー、seeds.rb で find_or_create_by を書かなくて良い、程度のおもちゃかな…。

構造

db/ 以下に色々置く感じの構造。

$ tree db/seeds
db/seeds
├── development.rb
├── factories
│   ├── cities.rb
│   ├── countries.rb
│   └── prefectures.rb
├── factories.rb
├── production.rb
└── test.rb
  • rake db:seed すると db/seeds/#{env}.rb が require されてコードが実行される
    • 共通処理書きたければ、各ファイルから共通ファイル require する、とか?
  • db/seeds/factories.rb には共通処理を書いておく
  • db/seeds/factories/**/*.rb にファクトリを置く
    • ファクトリは initialize_with { find_or_initialize_by } して冪等保証してるだけ

db/seeds.rb

これで spec/factories/ の方との干渉を防げるか自信なし。

表出力は本題とは関係なくて適当に。 Thread.current も特に意味はなく適当に。

require 'factory_girl'

include FactoryGirl::Syntax::Methods

begin
  original_definition_file_path = FactoryGirl.definition_file_paths
  FactoryGirl.definition_file_paths = %W(db/seeds/factories)
  FactoryGirl.reload

  begin
    Thread.current[:seeding_results] = Hash.new {|h, k| h[k] = [] }

    ActiveRecord::Base.transaction do
      require_relative "seeds/#{Rails.env}"
    end

    Thread.current[:seeding_results].each do |klass, ids|
      puts Hirb::Helpers::AutoTable.render(klass.where(id: ids))
    end
  end
ensure
  FactoryGirl.definition_file_paths = original_definition_file_path
end

使い方

ファクトリを書く

冪等保証のために initialize_with { find_or_initialize_by } を使っているので、モデルごとにファクトリを作る必要がある。

ファクトリは db/seeds/factories/ 以下に配置する想定。

FactoryGirl.define do
  factory :prefecture do
    initialize_with { Prefecture.find_or_initialize_by(country: country, name: name) }
  end
end

seed を書く

実際の seed 処理は db/seeds/#{Rails.env}.rb に書く想定なので、それぞれ書く必要がある。

create(:country, name: '日本') do |japan|
  create(:prefecture, country: japan, name: '東京都') do |tokyo|
    create(:city, prefecture: tokyo, name: '千代田区')
    create(:city, prefecture: tokyo, name: '港区')
  end
  create(:prefecture, country: japan, name: '神奈川県') do |kanagawa|
    create(:city, prefecture: kanagawa, name: '横浜市')
    create(:city, prefecture: kanagawa, name: '川崎市')
  end
end