はじめに
この記事の続きです。
https://qiita.com/ratovia/items/c9dbb00e4f663a7a7ef9
ancestryカラムを持ったFactoryの作り方
Factoryの基本的な構文はこれです。
FactoryBot.define do
factory :user do
first_name { "John" }
last_name { "Doe" }
admin { false }
end
end
例えば、itemテーブル(商品)を作成するとしたら
- まず、前回の記事の部分まで作ります。
FactoryBot.define do
factory :item do
name {"レモン"}
price {3000}
user
after(:build) do |item|
item.images.attach(io: File.open('spec/fixtures/test_image.jpg'), filename: 'test_image.jpg', content_type: 'image/jpg')
end
end
end
- 次に、カテゴリーのfactorybotを作成します。
# not work
FactoryBot.define do
factory :category do
name {"レモン"}
ancestry {"???"}
end
end
この???の部分が困りますよね?
ancestryのgemに従うと、
ancestry {"1/2"}
とかancestry {1}
とか悩むと思います。
ここはancestryに自動で生成してもらいましょう!
一旦、nilでいいと思います。
# this work
FactoryBot.define do
factory :category do
name {"レモン"}
ancestry {nil}
end
end
categoryとのアソシエーションの仕方
このように、after(:build)のなかで
親の生成、子供の生成、紐づけるカテゴリーの生成をやってみるのが良さそうです。
FactoryBot.define do
factory :item do
name {"レモン"}
price {3000}
user
after(:build) do |item|
# 親、子、孫を作成して、孫をitemのcategory_idに紐付ける
parent_category = create(:category)
child_category = parent_category.child.create(name: "hello")
grand_child_category = child_category.child.create(name: "world")
item.category_id = grand_child_category.id
item.images.attach(io: File.open('spec/fixtures/test_image.jpg'), filename: 'test_image.jpg', content_type: 'image/jpg')
end
end
end
いい感じですね!
ただし、このような記述だとchild_category = parent_category.child.create(name: "hello")
のように、
FactoryBotではない方法で、インスタンスを作っています。
このままでも良いですが、これを改善するには、FactoryBotをもっと効率よく作っていかないといけません。
factoryの入れ子(継承)にして、child_categoryとgrand_categoryを定義していきます。
factoryの入れ子(継承)については、以下一次ソースのInheritanceの項が参考になると思います。
https://github.com/thoughtbot/factory_bot/blob/master/GETTING_STARTED.md
|f|
のように変数定義すると、作成しようとするfactory自体を引数に取ることができ、
f.parent
のように、ancestryのメソッドを用いて、親を定義しましょう!
# this work
FactoryBot.define do
factory :category do
name {"レモン"}
ancestry {nil}
factory :child_category do |f|
f.parent create(:category)
factory :grand_category do |g|
g.parent create(:child_category)
end
end
end
end
※以下2つは同義です。
factory :category
factory :child_category do |g|
g.name ~~~~
end
end
factory :category do
trait :child_category do
after(:build) do |c|
c.name ~~~~
end
end
end
itemのfactoryの方は、孫だけ生成するだけでいけそうです。
# this work
FactoryBot.define do
factory :item do
name {"レモン"}
price {3000}
user
after(:build) do |item|
# create(:grand_child_category)のfactoryの中で、親の定義、さらに親の定義が始まる。
item.category_id = create(:grand_child_category).id
item.images.attach(io: File.open('spec/fixtures/test_image.jpg'), filename: 'test_image.jpg', content_type: 'image/jpg')
end
end
end
parent_idを用いた多階層モデルの場合
一般に、多階層の場合は、ancestryを使用すると思いますが、parent_idを使った場合も記載しておきます。
ニッチなので、結論のみ
FactoryBot.define do
factory :item do
name { "アイテム" }
after(:build) do |item|
parent = create(:category, name: "親", parent_id: nil)
child = create(:category, name: "子", parent_id: parent.id)
grand_child = create(:category, name: "孫", parent_id: child.id)
item.category_id = grand_child.id
item.images.attach(io: File.open('spec/fixtures/test_image.jpg'), filename: 'test_image.jpg', content_type: 'image/jpg')
end
end
end
FactoryBot.define do
factory :category do
name { nil }
parent_id { nil }
end
end
rails db:seed RAILS_ENV=testとかで用意したカテゴリーでテストする場合
自分には難しいんで、後日追記!