esm アジャイル事業部 開発者ブログ

永和システムマネジメント アジャイル事業部の開発者ブログです。

全レコードのバリデーションをチェックするgemを作った話

こんにちは @color_box です。 仕事からアイディアを得てgemを作ったのでそれについて書きます。

rspec-all_records_validator

github.com

rspec-all_records_validator というgemを作りました。DB内の全レコードに対してvalidationを実行するgemです。

これを導入するとある種のバグをE2Eテストで検出可能になります。RailsとRSpecが入っているプロジェクトを前提としています。

作った背景

仕事で遭遇したとあるバグが、このgemを作成した背景にあります。

Active Recordのバリデーションにおいて、関連の個数を確認するようなバリデーションはすり抜けの可能性があります。

例として下記のようなクラスを仮定します。Humanオブジェクトはdogを一つ以上関連として持つべき、というバリデーションが設定されています。

class Human < ApplicationRecord
  has_many :dogs
  validates :dogs, length: {minimum: 1}
end

class Dog < ApplicationRecord
  belongs_to :human
end

この時、下記のようなコードを実行するとbreederのレコードはinvalidになります。

dog = Dog.new
breeder = Human.create!(dogs: [dog])

dog.reload
=> #<Dog id: 1, human_id: 1>

dog_owner = Human.new
dog_owner.dogs = breeder.dogs
dog_owner.save!

dog.reload
=> #<Dog id: 1, human_id: 2>

breeder.reload.invalid?
=> true

breederからdog_ownerdogsオブジェクトが移動してしまっており、breederdogsを持たなくなるためinvalidになります。 invalid であるにも関わらずbreederのvalidationは実行されないためそれに気づけません。

基本的にDBの中に永続化されているレコードは全てvalidであるはずなので、このようにinvalidなデータがDBに残り続けると別の箇所でバグを発生させます。

テスト実行後にバリデーションを実行できると、このような状況を防ぐことが可能です。rspec_all_record_validatorはそのようなバリデーション実行を簡易に追加できるgemです。E2Eテスト終了時、全レコードのvalidationをチェックすることでこの手のバグを検知しています。

ただ、E2Eテストの実行後に全てのレコードのvalidationを実行するため、当然ビルド時間が増えます。そういった課題は今後改善していければと思います。

まとめ

今回、仕事での気づきを元に作成したgemについて、その背景や作った理由などを書かせていただきました、この記事やgemがどなたかのお仕事の役に立てば幸いです。