FactoryBotで大量のテストデータを作りたい時にはcreate_listメソッドが使える
create_list(:food, 100)
これでFoodのレコードが100件保存される。
では、消費期限が異なるFoodのレコードを100件作りたい場合はどうするか。
以下のようにcreate_listメソッドにはブロックが渡せる。
create_list(:food, 100) do |food, i| food.expiration_date = Date.today + i end
これで、expiration_dateの値が異なるFoodのレコードを100件作れる。と思いきや、、、
expect(Food.all.pluck(:expiration_date)).to eq [] => expected: [] got: [nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil、、、(省略)
レコードは作られるものの、expiration_dateが全てnilになってしまう。
create_listにブロックを渡したときの挙動
下の記事の表現を引用させていただく。
create_listメソッドは以下のような挙動になる
def create_list object = build the object object.save! yield(object) if block_given? end
つまり、foodをsaveした後に、そのsaveしたfoodをブロックに渡すという挙動になる。
そのため、すでにsaveされたfoodに対してexpiration_dateをセットしているものの、再度saveされていない(expiration_dateを更新していない)ためexpiration_dateはnilのままになる
解決策
以下のようにブロックの中でsave!をする。
create_list(:food, 100) do |food, i| food.expiration_date = Date.today + i food.save! end
しかし、上のやり方だと、ひとつのオブジェクト(food)に対して2回saveが走る。
以下のようにbuild_listを使うと1オブジェクト1saveでテストデータを作成できる。
build_list(:food, 100) do |food, i| food.expiration_date = Date.today + i food.save! end
ただ、このやり方で100件もsaveするとSQLの発行回数も100件になってしまい、テストが非常に重くなる。
ということで以下のようにバルクインサートする。
foods = build_list(:food, 100) do |food, i| food.expiration_date = Date.today + i end Food.insert_all foods.map(&:attributes)