【FactoryBot】外部参照キーのカラムデータの作成方法

# 【FactoryBot】外部参照キーのカラムデータの作成方法

FactoryBotで外部参照キーとなるカラムデータを作成する方法を調べたので内容をまとめます.
例えばPostモデルでファクトリデータを作成する際に一緒に投稿者となるUserのファクトリデータを作成するような状況です。

## 目次
1. [状況](#状況)
* [動作環境](#動作環境)
* [手順](#手順)
* [結果](#結果)
* [当初つまづいた点](#当初つまづいた点)
* [おわりに](#おわりに)


## 状況

Messageモデルのテストを行うためにConversationモデルとUserモデルのデータが必要です。
下記が今回のER図です。

Messageモデルは`belongs_to :user, belongs_to :conversation`というアソシエーションを持ちます。

すなわちMessageモデルのFactoryBotを実行した際にConversationモデルとUserモデルのデータを生成する必要があります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526570/163826f8-a6ac-5605-72be-b65da15fe1a5.png)

```ruby:message.rb
class Message < ApplicationRecord
  belongs_to :conversation
  belongs_to :user
end
```
```ruby:user.rb
class User < ApplicationRecord
  has_many :senders, class_name: 'Conversation', foreign_key: :sender_id
  has_many :recipients, class_name: 'Conversation', foreign_key: :recipient_id
  has_many :messages, dependent: :destroy
end
```
```ruby:conversation.rb
class Conversation < ApplicationRecord
  belongs_to :sender, class_name: 'User', foreign_key: :sender_id
  belongs_to :recipient, class_name: 'User', foreign_key: :recipient_id
  has_many :messages, dependent: :destroy
end
```

---------------------------------------
## 動作環境
`OS : macOS Mojave 10.14.6`
`ruby : 2.6.3p62`
`rails : 5.2.4`
`factory_bot_rails : 5.2.0`

## 手順

イメージとして以下の流れを実現するようにします。
`STEP1. UserのFactoryBot呼び出し`
`STEP2. ConversationのFactoryBot呼び出し `
`STEP3. MessageのFactoryBot呼び出し`

### STEP 1. UserモデルのFactoryBotを定義

```ruby:users.rb
FactoryBot.define do
  #名前が重複しないようにシーケンスを利用
  factory :user do
    sequence :name do |n|
      "user_#{n}"
    end
  end
end
```

### STEP 2. ConversationモデルのFactoryBotを定義

associationを記載することでUserのFactoryBotが呼び出されsenderに代入されます.
__ポイント__ `association(senderやrecipient)`と`user`は異なる名前のため`factory: :user`でどのfactoryを利用するのか明示しています

```ruby:conversations.rb
FactoryBot.define do
  factory :conversation do

  association :sender, factory: :user
  association :recipient, factory: :user

  end
end

```

実際にConversaionモデルのFactoryBotを動かしてみました。
conversation.senderとconversation.recipientにFactoryBot :userで作成されたデータが入っています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526570/eb74d664-2eac-56b3-5357-aa1c6aac4a63.png)

### STEP 3. MessageモデルのFactoryBotを定義

```ruby:messages.rb
FactoryBot.define do
  factory :message do
    sequence :body do |n|
      "message_#{n}"
    end
    #アソシエーション名とファクトリー名が異なる場合はconv factory: :conversationのように
    #記述.今回はアソシエーション名とファクトリ名が同じためconversationのみで問題ない.
    conversation
    #conversationファクトリで作成したuserデータをmessage.userに代入
    user { conversation.sender }

  end
end
```

## 結果

```ruby:message_spec.rb
require 'rails_helper'

RSpec.describe Message, type: :model do
  it 'バリデーションが通る' do
    message = build(:message, body:'test message' )
    binding.pry
    expect(message).to be_valid
  end
end
```

message内を確認するとuserとconversationにデータが入っており、
テストをPassしている。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526570/9684dd5f-2b43-44b9-110a-6eb6b83d5215.png)


## 当初つまづいたところ

当初はSTEP 3. FactoryBot:messageのコードでblankエラーが発生していた


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526570/7c3bbde0-05a2-3709-5b0e-8c2cd55bf93e.png)

### 原因

そこでMessagemモデルのファイルを確認すると`user_id`と`conversation_id`にnullチェックがあったためBlankエラーになっていた。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526570/3d85da49-1f50-7464-6738-f9efcd7ab60a.png)

つまりこのようなバリデーションがある場合はcreateメソッドを利用する必要があります。

```ruby:messages.rb
FactoryBot.define do
  factory :message do
    sequence :body do |n|
      "message_#{n}"
    end

    after(:build) do |message|
      message.conversation = create(:conversation)
      message.user = message.conversation.sender
    end

  end
end
```

インスタンスが保存されるためidが表示される. その結果,nullチェックのバリデーションもPassできる.

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526570/9aaf2365-debe-0c40-0ca4-2dafaaa181fc.png)


## おわりに

今回の件で以下のことを学びました。
1. associationメソッドを利用することで外部参照キーのデータを作成できる
2. associationメソッドはbuildのため、idのnullチェックがある場合はcreateを利用する