親削除時に自分も削除 shared_example: 小林研 Rails Tips (7)

はじめに

Rails Tips の 7 回目です。連日の 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

親削除時に自分も削除

昨日は belongs_to 側の確認でしたが、親の削除に関するテストが三種類あります。今日はその一つ目です。読んで字の如しで、親が削除された時に子供が削除されることを確認するテストです。昨日紹介した「関連確認」の学生住所は、今回の「親削除時に自分も削除」とペアでこんな感じで使います。昨日の「関連確認」は address_tel が削除された時に、gakusei.address_tel が nil になることを確認しました。今回の「親削除時に自分も削除」では gakusei を削除した時に address_tel が削除されることを確認します。

require "rails_helper"

RSpec.describe AddressTel, type: :model do

context "属性に関する共通テスト" do
    subject { address_tels :can_delete }
    it_behaves_like "関連確認", :address_tel, has_one: %i[gakusei]
    it_behaves_like "親削除時に自分も削除", :address_tel, %i[gakusei]
  end
end

この shared_example は relation の名前から subject の親を取り出し、その親を実際に削除します。その際、subject のモデルの個数が変化することを確認します。subject 以外にも関連により複数のオブジェクトが削除される可能性もあるため、あくまで変化したことだけを確認します。

shared_examples_for "親削除時に自分も削除" do |model, relations|
  relations.each do |relation|
    context "#{model}.#{relation}を削除したとき" do
      it "関連により自分自身も削除されること" do
        parent = subject.send(relation)
        expect { parent.destroy }.to change(model.to_s.pluralize.classify.constantize, :count)
      end
    end
  end
end

この shared_example を実行するためには、subject.relation である親が削除できる必要があります。依存関係などで削除できなくなっているとこのテストも失敗してしまうからです。

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

このテストを通過させるためには、has_many や has_one の dependent オプションで :destroy を設定すればよいです。簡単ですね。

  # @return [AddressTel] 対応する住所
  has_one :address_tel, dependent: :destroy

おわりに

今日は、昨日の belongs_to の反対側の設定になります。親削除時に自分を削除する設定はゴミデータを残さないためには重要なものです。ただし、間違えて親が削除された時の被害は莫大なので、本当にこの設定をしていいのかどうかはよく考える必要があります。注意してください。