FactoryBotでhas_oneの関連付けを表現するのってどうやるんだっけ?ってなったので自分用に備忘録を残しました。
traitという機能を使用しました。
今回はtraitの基本的な使い方➕作業の全体像を記しています。
参考にした記事
前提条件
CustomerがProfileをhas_oneしています。
class Customer < ApplicationRecord
has_one :profile, dependent: :destroy
end
class Profile < ApplicationRecord
belongs_to :customer
end
本題のコード
customerの方のFactoryBot
FactoryBot.define do
factory :customer do #各テストからcreate(:customer)で生成できるようになる
trait :profile do #traitで条件を付与したものを生成できるようになる。create(:customer, :profile)の形で生成できる
after(:build) do |customer|
customer.build_profile
end
end
end
end
after(:build)は、オブジェクトがメモリ上にビルドされた直後に、特定のアクションを実行するためのコールバックです。
つまり、この場合はブロック変数のcustomerがbuildされたもので、それがデータベースに保存される前にcustomer.build_profileをして、customerインスタンスに新しくprofileを紐づけています。
profileの方のFactoryBot
FactoryBot.define do
factory :profile do
customer
end
end
上記については
だいぶ省略されていて、
factory :profile do
association :customer
end
このassociationメソッドが省略されています。
これによってProfileモデルがCustomerモデルに対してbelongs_toの関係を持つことをしめされています。
テスト上での使い方
とてもシンプルな例。
自分で定義した「profile!」というインスタンスメソッドの挙動をテストしています。
CustomerがProfileを持っていないか確認してからprofileを作成するという内容のものです。
テストしたいインスタンスメソッドは以下の通り
class Customer < ApplicationRecord
has_one :profile, dependent: :destroy
end
def profile!
return if profile?
create_profile!
end
実際のテスト
require 'rails_helper'
RSpec.describe Customer do
describe 'profile!' do
it 'profileを追加できる' do
customer = create(:customer) #profileを持っていないので、profile!を実行したら追加できるはず
expect { customer.profile! }.to change(Profile, :count).by(1) #count自体が増えることでそれを証明している
end
it 'customerが既にProfileを持っている場合は追加できない' do
customer = create(:customer, :profile) #先ほどのtraitを使用して、既にprofileを持ったcustomerを生成している。
target_id = customer.profile.id #has_oneのものは新しく生成されると更新されてしまうので、現在のprofileのidを比較するために変数に格納しておく
customer.profile!
expect(Profile.exists?(id: target_id)).to be true #既にprofileがあるなら生成されずに同じidのインスタンスを保持しているはず
end
end
end
has_oneの挙動についても理解が浅く実装に少し苦戦しました。
次回はこの記事読んでささっと実装できたら良いな〜と思っています。
間違い、こうするともっと良いなどがあったらぜひご指摘していただけると幸いです。