親は削除不可 shared_example: 小林研 Rails Tips (8)

はじめに

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

一方、kurasugakusei には has_manydependent オプションとして、restrict_with_error を設定します。これにより、gnumber が存在する時には、kurasu や gakusei は削除できなくなります。

  # @return [ActiveRecord::Relation<Gnumber>]  対応する学生番号一覧
  has_many :gnumbers, dependent: :restrict_with_error

おわりに

子データが存在するときに親が削除できなくすることは非常に多いので、この制約はかなり使うと思います。明日はモデル共通テストの最後になります。