Discussion:
[Rails] Database based uniqueness validations for ActiveRecord
DjezzzL
2018-11-21 16:25:12 UTC
Permalink
You may have noticed that Rails is performing a *SELECT 1 FROM *query
before inserting a new record if there's a uniqueness validation in the
model.


This approach has several downsides.

First of all, it's an additional query, and in case there's uniqueness
validation on several attributes, several additional queries will be
performed.

Second, which is important for applications running at scale, is a race
condition, two concurrent create/update operations may end up querying the
database for existing records and then add a record to the database that
will leave it in an inconsistent state. Of course, this may be resolved
with a DB uniqueness constraint, but in this case, you won't get a proper
validation message, the request will just error out.


Welcome database_validations
<https://github.com/toptal/database_validations> gem, which provides
compatibility between database constraints and ActiveRecord validations
with better performance and consistency.


Skip to the benchmarks
<https://github.com/toptal/database_validations#benchmark-code> to find out
that for the case where each hundredth statement is attempting to insert a
duplicate is two times faster with this gem.


In majority of cases, *validates_db_uniqueness_of* is a drop-in replacement
for validates_uniqueness_of.

*scope*, *message*, *if* and *unless* conditionals and *index_name* are
supported on major databases. *where* is supported on PostgreSQL.


A convenience *RSpec* matcher is bundled:

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

You must add DB uniqueness constraints on the database level, and if you
didn't, the gem will error out with an explanatory message.

The gem is battle tested on an application with almost a hundred uniqueness
validations in 50+ models at scale.

Known gotchas:

-

if there's more than one validation failure, only one will be indicated

Related read:


Rails guide on uniqueness validations
<https://guides.rubyonrails.org/active_record_validations.html#uniqueness>.

The Perils of Uniqueness Validations
<https://robots.thoughtbot.com/the-perils-of-uniqueness-validations> blog
post.

Use create_or_find_by to avoid race condition in Rails 6.0
<https://sikac.hu/use-create-or-find-by-to-avoid-race-condition-in-rails-6-0-f44fca97d16b>
blog post. Add #create_or_find_by to lean on unique constraints
<https://github.com/rails/rails/pull/31989> Rails pull request.

Validation, Database Constraint, or Both?
<https://robots.thoughtbot.com/validation-database-constraint-or-both> blog
post. related Reddit comments
<https://www.reddit.com/r/ruby/comments/5u8mb8/validation_database_constraint_or_both>
.

Do you test simple validations and db structure of models?
<https://www.reddit.com/r/rails/comments/8141gl/do_you_test_simple_validations_and_db_structure>
discussion.

Contributions are welcome. What's on the list:

-

RuboCop cop to detect the uses of valdiates_uniqueness_of and
autocorrect them to use DB uniqueness validation

Get over with *validates_uniqueness_of* and embrace
*validates_db_uniqueness_of*!


P.S. I'm the owner of the gem but the topic was copy-pasted from reddit
<https://www.reddit.com/r/rails/comments/9v8piy/database_based_uniqueness_validations_for/>
to spread the information about the gem.
--
You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group.
To unsubscribe from this group and stop receiving emails from it, send an email to rubyonrails-talk+***@googlegroups.com.
To post to this group, send email to rubyonrails-***@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/rubyonrails-talk/481ef5b6-8b3a-4ce9-84d6-958c3efb9acb%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
Loading...