#前回までのあらすじ
親モデルと子モデルの単体テストはできたのですが、親モデルが子モデルのレコードがなくても保存できてしまっていることに気付きました。
今回は子レコードが保存できない場合に親レコードで保存できないようにバリデーションを組もうとして四苦八苦したことを記事に残していこうと思います。
#下準備
前回記事とか前々回記事とかその前の記事とかの下準備を参照ください。
#今回のテスト環境の準備
###バリデーションの追加
validates :images,presence: true
を追記します。
(中略)
validates :title,:images,presence: true #:imagesを追記
###インスタンスの作成
いくつかのパターンを想定してインスタンスを作成しました。
images.rbのインスタンスの内訳は以下です。
:image
レコードの保存が期待できるインスタンス
:without_image
imageがnullで保存に失敗することが期待されるインスタンス
:with_text
imageにテキストファイルを保存しようとして保存に失敗することが期待されるインスタンス
FactoryBot.define do
factory :image do
image { Rack::Test::UploadedFile.new(File.join(Rails.root, 'spec/fixtures/image.jpg'))}
association :item
factory :without_image ,class:Image do
image {""}
association :item
end
factory :with_text ,class:Image do
image { Rack::Test::UploadedFile.new(File.join(Rails.root, 'spec/fixtures/image.txt'))}
association :item
end
end
end
items.rbのインスタンスの内訳は以下です。
:item
保存が期待できるインスタンス
:item_without_name
nameがnullで保存に失敗することが期待されるインスタンス
:item_without_image
:without_imageのインスタンスで子モデルのレコードの保存に失敗して、親レコードも保存に失敗することが期待されるインスタンス
:item_with_text
:with_textのインスタンスで子モデルのレコードの保存に失敗して、親レコードも保存に失敗することが期待されるインスタンス
FactoryBot.define do
factory :item do
name {"test"}
after(:build) do |item|
item.images << build(:image,item: item)
end
factory :item_without_name,class:Item do
name {""}
end
factory :item_without_image,class:Item do
after(:build) do |item|
item.images << build(:without_image,item: item)
end
end
factory :item_with_text,class:Item do
after(:build) do |item|
item.images << build(:with_text,item: item)
end
end
end
end
###テストコードを記述
require 'rails_helper'
RSpec.describe Image, type: :model do
describe '#create' do
let(:image) {build_stubbed(:image)}
context 'can save' do
it "is valid with a image,item_id" do
expect(image).to be_valid
end
end
context 'can not save' do
it "is invalid without a image" do
expect(build_stubbed(:without_image)).to be_invalid
end
it "is invalid with a image with filetype of not image file" do
expect(build_stubbed(:with_text)).to be_invalid
end
it "is invalid without a item_id" do
image.item_id = ""
expect(image).to be_invalid
end
end
end
end
require 'rails_helper'
RSpec.describe Item, type: :model do
describe '#create' do
let(:item) {build(:item)}
context 'can save' do
it "is valid with a name,images" do
expect(item).to be_valid
end
end
context 'can not save' do
it "is invalid without a name" do
expect(build(:item_without_name)).to be_invalid
end
it "is invalid without images" do
expect(build(:item_without_image)).to be_invalid
end
it "is invalid with a file is type of text" do
expect(build(:item_with_text)).to be_invalid
end
end
end
end
準備ここまで。
#テストの実行結果
bundle exec rspec spec/models/
Image
#create
can save
is valid with a image,item_id
can not save
is invalid without a image
is invalid with a image with filetype of not image file
is invalid without a item_id
Item
#create
can save
is valid with a name,images (FAILED - 1)
can not save
is invalid without a name
is invalid without images
is invalid with a file is type of text
Failures:
1) Item#create can save is valid with a title
Failure/Error: expect(item).to be_valid
expected #<Item id: nil, title: "test", created_at: nil, updated_at: nil> to be valid, but got errors: Images item can't be blank
# ./spec/models/item_spec.rb:8:in `block (4 levels) in <top (required)>'
Finished in 1.17 seconds (files took 5.74 seconds to load)
8 examples, 1 failure
Failed examples:
rspec ./spec/models/item_spec.rb:7 # Item#create can save is valid with a name,images
:itemの保存ができずに保存に失敗してしまいました。
#原因を探る
imageモデルのvalidates :item_id,presence: true
が原因でitemの保存ができていませんでした。
class Image < ApplicationRecord
(中略)
validates :image,presence: true #:item_idを削除
end
ただ、外部キーのitem_id
がnull
でも保存できてしまい、imageモデルのテストが失敗する懸念があります。
#結果
bundle exec rspec spec/models/
Image
#create
can save
is valid with a image,item_id
can not save
is invalid without a image
is invalid with a image with filetype of not image file
is invalid without a item_id
Item
#create
can save
is valid with a name
can not save
is invalid without a name
is invalid without images
is invalid with a file is type of text
Finished in 1.11 seconds (files took 5.81 seconds to load)
8 examples, 0 failures
杞憂でした。
#教訓
親モデルにバリデーションをかければ、子モデルの外部キーのバリデーションは必要ない(多分)
今回は以上です。