🎉

rake taskで has_many/has_one の設定漏れと dependent オプションの設定漏れを検出する gem を作った

2025/04/07に公開

以前 ActiveRecordの関連の設定漏れと削除時の外部制約違反を防ぐ(暫定版) という記事を書いたが、これを gem 化して rake task で問題を検出するようにしたので紹介する。

https://rubygems.org/gems/dependent_option_checker

https://github.com/muryoimpl/dependent_option_checker

検出すると、以下のように出力する。

$ rails dependent_option_checker:check
Detected `dependent` option or omissions of has_many/has_one denifition.

# Employee (table: employees)
  - has_many/has_one omission
    + addresses

やりたかったこと

目的は、レコードを削除する際に外部制約違反でエラーになることを避けたいため、実際にレコードを削除する前に問題を検知したいということ である。

rubocop-rails の Rails/HasManyOrHasOneDependent が有効になっているならば has_many/has_one の記述があれば dependent オプション設定漏れを検出してくれるが、has_many/has_one の記述漏れがある場合、この取締は発動しない。
この has_many/has_one の設定漏れがある場合も検出したい、というのが開発の動機である。

この gem が実現すること

以下の 2 点の問題を検出する rake task である dependent_option_checker:check を提供する。

  1. テーブルに対応した ActiveRecord のモデルに対し、has_many/has_one に対し、dependent オプションの設定漏れを検出する
  2. テーブルに対応した ActiveRecord のモデルに対し、has_many/has_one の記述漏れを検出する

実際には、GitHub Actions でテスト実行している step の後にこの rake task を実行して検出するようにしている。

前回記事との変更点

以下の点が前回記事と変わっていると認識している。

  1. 検出方法を単体テストから rake task に変更した
  2. モデルクラスとテーブルの取得方法を変更した
  3. has_many/has_one のみを対象にした

検出方法を単体テストから rake task へ変更した

これは以前の単体テストとして実現したものが、 "メソッドの単体テスト" という目的の範疇を超えているため、rake task に置き換えたというのが経緯である。

CI の設定上、単体テスト実行 -> この rake task 実行という流れになっているので、検出タイミングは単体テストで実現していた当時よりは後ろになってしまうが、マージされる前には検出されるのでよしとした。

モデルクラスとテーブルの取得方法を変更した

以前は ApplicationRecord.connecion.tables でテーブルを取得して名前からモデル定数を取得していたが、ApplicationRecord.descendants での取得に切り替えた。

これは、.table_name= で割り当てるテーブルを変更できてしまうことと、ar_metadata 等のアプリケーションでは使わない管理用テーブルの取得を避けたかったという理由がある。特に後者は除外設定の記述を減らす効果があるため、対応しておきたかった。

テーブルはクラス定数から取得することになったため、.table_name から取得するように変わっている。

has_many/has_one のみを対象にした

以前の実装の場合は ActiveRecord::Reflection::ThroughReflection も対象に含めていたのだが、has_many through で取得したレコードについては以下のようなエラーが出てそのまま destroy_all を実行できない == dependent オプションが効果を成さないと判断したため除外した。

Cannot modify association ‘A#cs’ because the source reflection class ‘C’ is associated to ‘B’ via :has_many. (ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection)

まとめ

dependent オプション設定漏れ、has_many/has_one 設定漏れを検出する rake task を備えた gem を作成した。

GitHub Actions でテストと一緒に実行するように設定して運用している。dependent オプションや has_many/has_one の設定漏れが強制的に検出されるようになったため、今後これに関する漏れはなくなる見込みである。

こういう「仕組み」を作って楽しみ、実行させて開発を補助していきたい。

あしたのチーム Tech Blog

Discussion