はじめに
Rails Tips の 8 回目です。連日の 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
親は削除不可
昨日の「親削除時に自分を削除」に続き、親の削除に関するテストです。こちらも読んで字の如しで、子供がいると親が削除できないことを確認するテストです。一昨日の「関連確認」において、 gnumber が削除された時に、gakusei.gnumbers, kurasu.gnumbers, year.gnumbers がそれぞれ1つずつ減ることを確認しました。今回の「親は削除不可」では gnumber が存在するときに、gnumber.gakusei, gnumber.kurasu, gnumber.year が削除できないことを確認します。
require "rails_helper" RSpec.describe AddressTel, type: :model do describe "属性に関する共通テスト" do subject { gnumbers :can_delete } it_behaves_like "関連確認", :gnumber, has_many: %i[gakusei kurasu year] it_behaves_like "親は削除不可", :gnumber, %i[gakusei kurasu year] end
この shared_example は relation の名前から subject の親を取り出し、その親を実際に削除します。その削除の際にエラーが発生することを確認しています。
shared_examples_for "親は削除不可" do |model, relations| relations.each do |relation| it "#{model}.#{relation} が削除できないこと" do parent = subject.send(relation) parent.destroy expect(parent.errors[:base].size).to eq(1) end end end
正しく動作させるための実装
親が削除不可である条件は、そもそも削除できないモデルであるか、リレーションの依存関係で削除させないかのどちらかです。これについては、すでに 削除可能制約、削除不可制約 shared_example: 小林研 Rails Tips (5) - hkob’s blogで説明していました。削除できない Year については、以下のように before_destroy で削除できないように設定します。
# @note 年度は削除しない before_destroy :on_destroy_validation_method def on_destroy_validation_method errors.add :base, "年度は削除できません" throw :abort end private :on_destroy_validation_method
一方、kurasu
や gakusei
には has_many
の dependent
オプションとして、restrict_with_error
を設定します。これにより、gnumber が存在する時には、kurasu や gakusei は削除できなくなります。
# @return [ActiveRecord::Relation<Gnumber>] 対応する学生番号一覧 has_many :gnumbers, dependent: :restrict_with_error
おわりに
子データが存在するときに親が削除できなくすることは非常に多いので、この制約はかなり使うと思います。明日はモデル共通テストの最後になります。