一意制約 shared_example: 小林研 Rails Tips (3)

はじめに

Rails Tips の 3 回目です。昨日の model のテストを簡単に書くための shared_example の紹介の続きです。しばらくは私の Shared example の雛形 (lib/templates/rspec/model/model_spec.rb)を一つずつ解説していく形です。

require "rails_helper"

# RSpec.describe <%= class_name %>, type: :model do
#  let(:<%= singular_table_name %>) { <%= plural_table_name %> :<%= singular_table_name %> }
#  let(:can_delete) { <%= plural_table_name %> :can_delete }

#  context "属性に関する共通テスト" do
#    subject { can_delete }
#    let(:another_object) { <%= plural_table_name %> :another_object }

#    it_behaves_like "存在制約", %i[]
#    it_behaves_like "一意制約", %i[]
#    it_behaves_like "複合一意制約", %i[]
#    it_behaves_like "削除可能制約"
#    it_behaves_like "削除不可制約"
#    it_behaves_like "関連確認", :<%= singular_table_name %>, has_many: %i[], has_one: %i[], children: :optional, child: :optional
#    it_behaves_like "親削除時に自分も削除", :<%= singular_table_name %>, %i[has_many has_one]
#    it_behaves_like "親は削除不可", :<%= singular_table_name %>, %i[has_many has_one]
#    it_behaves_like "親削除時にnullを設定", :<%= singular_table_name %>, %i[has_many has_one]
#  end

一意制約

モデルの属性のうち、同じ値を許可しないもの(値が unique でなければならない)をチェックするための shared_example です。例えば、year モデルの西暦を示す属性である year が同じ値であっては困る時には、以下のようなテストになります。一意オブジェクトは二つインスタンスがないとテストできないので、雛形にある another_object を用意する必要があります。

require "rails_helper"

RSpec.describe Year, type: :model do
  let(:year) { years :y2023 }

  context "属性に関する共通テスト" do
    subject { year }
    let(:another_object) { years :y2022 }

    it_behaves_like "一意制約", %i[year]
  end
end

これを実現するための「存在制約」 shared example は以下のようになります。

shared_examples_for "一意制約" do |keys|
  keys.each do |key|
    it "別のオブジェクトが同じ #{key} を持っていたとき、エラーになること" do
      subject[key] = another_object[key]
      expect(subject).not_to be_valid
    end
  end
end

この example では keys で示されたすべての key に対して、subject の key 属性を another_object と同じ値に設定します。制約が正しく設定されていれば、一意性の制約で変更が不正であることが確認できます。逆に、制約が設定されていない場合には、この変更は有効でありこのテストが失敗することになります。

正しく動作させるための実装

属性が一意になることを検証することになるので、以下のコードを実装に記述することでテストを追加させることができます。

class Year
  validates :year, uniqueness: true
end

おわりに

昨日の記事で boolean に対する存在制約のことも書いておいた方がいいと思い、追記をしています。興味があればそちらもご覧ください。