【Rails、RSpec】Ancestryを用いている時のFactoryBotの作り方

# はじめに
この記事の続きです。
https://qiita.com/ratovia/items/c9dbb00e4f663a7a7ef9

# ancestryカラムを持ったFactoryの作り方
Factoryの基本的な構文はこれです。

```ruby
FactoryBot.define do
  factory :user do
    first_name { "John" }
    last_name  { "Doe" }
    admin { false }
  end
end
```

### 例えば、itemテーブル(商品)を作成するとしたら

- まず、前回の記事の部分まで作ります。

```ruby:factories/items.rb
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を作成します。

```ruby:factories/categories.rb
# not work
FactoryBot.define do
  factory :category do
    name {"レモン"}
    ancestry {"???"}
  end
end
```

この???の部分が困りますよね？
ancestryのgemに従うと、
`ancestry {"1/2"}`とか`ancestry {1}`とか悩むと思います。
ここはancestryに自動で生成してもらいましょう！
一旦、nilでいいと思います。

```ruby:factories/categories.rb
# this work
FactoryBot.define do
  factory :category do
    name {"レモン"}
    ancestry {nil}
  end
end
```

### categoryとのアソシエーションの仕方
このように、after(:build)のなかで
親の生成、子供の生成、紐づけるカテゴリーの生成をやってみるのが良さそうです。

```ruby:factories/items.rb
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のメソッドを用いて、親を定義しましょう！

```ruby:factories/categories.rb
# 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
```
### ※以下２つは同義です。

```ruby
factory :category
  factory :child_category do |g|
    g.name ~~~~
  end
end
```

```ruby
factory :category do
  trait :child_category do
    after(:build) do |c|
      c.name ~~~~
    end
  end
end
```

itemのfactoryの方は、孫だけ生成するだけでいけそうです。

```ruby:factories/items.rb
# 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を使った場合も記載しておきます。

ニッチなので、結論のみ

```ruby:spec/factories/items.rb
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
```

```ruby:spec/factories/categories.rb
FactoryBot.define do
  factory :category do
    name { nil }
    parent_id { nil }
  end
end
```

## rails db:seed RAILS_ENV=testとかで用意したカテゴリーでテストする場合
自分には難しいんで、後日追記！