Real validation for uniqueness

    Every rubist who has worked with Ruby on Rails is familiar with the ORM ActiveRecord . Let's discuss one of the validations proposed from the box, namely, validation for uniqueness, and why database_validations gem will save the consistency of your database.

    Suppose you have a user model with a unique identity on the email field , i.e.

    classUser < ApplicationRecord
      validates :email, uniqueness:trueend

    You may already know that this validation performs the following request.

    SELECT1FROMusersWHERE email = $1

    every time we try to save a record to the database.

    This approach has several drawbacks:

    First , the execution of an additional request, and in case the model has several initial validations for uniqueness, the request will be executed for each of them. This is not efficient, and also requires the presence of indices if we want these queries to be executed quickly.

    Secondly , this solution does not guarantee uniqueness due to the possible race for data . Several competitive operations can simultaneously find out about the absence of a specific record, as a result of which, keep the same data.

    Of course, it is possible to allow rare cases with data race by adding a unique constraint at the database level. But in this case, you will not get a validation error, the query to the database will simply fall and the entire transaction will be rolled back.

    Gem database_validations , which provides compatibility between database constraints and validations, will help in this situation .

    The main meaning of gem is presented in the following code:

    defsave(options = {})
      ActiveRecord::Base.connection.transaction(requires_new:true) { super }
    rescue ActiveRecord::RecordNotUnique => e
      Helpers.handle_unique_error!(self, e)
      falseend


    Thus, we try to save the data, if all other validations are passed, if the transaction drops and rolls back, we parse the error and assign the correct values ​​in errorsour object.

    After reviewing the documentation and benchmarks , it can be concluded that this gem will accelerate the process of preservation of records in the database at least two times.

    With support for databases such as PostgreSQL , SQLite , MySQL, and backward compatibility with validates_uniqueness_of, the replacement process validates_db_uniqueness_ofdoes not take a matter of minutes.

    A convenient matcher for RSpec is also present out of the box:

    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

    When you switch to a new validation, you need to have uniqueness restrictions in the database, but if they are not there yet, gem will indicate this during the launch of the application.

    The heme has been tested on an application with 100+ uniqueness validations among 50+ models.

    Use gems and share opinions. Any contribution to further development is welcome!

    Also popular now: