メールフィールドに一意性のあるユーザーモデルがあるとしましょう。
class User < ApplicationRecord validates :email, uniqueness: true end
この検証が次のリクエストを実行することを既に知っているかもしれません
SELECT 1 FROM users WHERE email = $1
データベースにレコードを保存しようとするたびに。
このアプローチにはいくつかの欠点があります。
まず 、追加のリクエストの実行、およびモデル内で複数の一意性検証が初期化されている場合、リクエストはそれぞれに対して実行されます。 これは効率的ではなく、これらのクエリをすばやく実行する場合はインデックスも必要です。
第二に 、このソリューションは、データの競合の可能性があるため、一意性を保証しません。 いくつかの競合事業は、特定のレコードが存在しないことを同時に学習し、その結果、同じデータを保持することができます。
もちろん、データの競合を伴うまれなケースは、データベースレベルで一意性制約を追加することで解決できます。 ただし、この場合、検証エラーは発生せず、データベースへのクエリは単純に失敗し、トランザクション全体がロールバックされます。
この状況では、gem database_validationsが役立ちます。これにより、データベースの制限と検証の間の互換性が提供されます。
gemの主な意味は、次のコードに示されています。
def save(options = {}) ActiveRecord::Base.connection.transaction(requires_new: true) { super } rescue ActiveRecord::RecordNotUnique => e Helpers.handle_unique_error!(self, e) false end
したがって、他のすべての検証に合格した場合、トランザクションが失敗してロールバックした場合、データを保存しようとします。エラーを解析し、オブジェクトの
errors
正しい値を割り当てます。
ドキュメントとベンチマークを確認した後、このgemにより、レコードをデータベースに少なくとも2回保存するプロセスが高速化されると結論付けることができます。
PostgreSQL 、 SQLite 、 MySQL 、および
validates_uniqueness_of
との下位互換性などのデータベースのサポートのおかげで、
validates_uniqueness_of
に置き換えるプロセスには数分かかります。
RSpecの便利なマッチャーもすぐに使用できます。
specify do expect(described_class) .to validate_db_uniqueness_of(:field) .with_message('duplicate') .with_where('(some_field IS NULL)') .scoped_to(:another_field) .with_index(:unique_index) end
新しい検証に切り替えるときは、データベースの一意性に制限を設ける必要がありますが、まだ存在しない場合、gemはアプリケーションの起動時にこれを示します。
この宝石は、50以上のモデル間での一意性について100以上の検証を行ったアプリケーションでテストされました。
gemを使用して意見を共有してください。 さらなる開発への貢献は大歓迎です!