Rails 6 has added ActiveRecord::Base.create_or_find_by/! as an alternative to ActiveRecord::Base.find_or_create_by/!
Relation#find_or_create_by is one of Rails finder methods,
that finds the first record with the given attributes,
or creates a record with the attributes if one is not found.
One of the problems of this approach is that its not an atomic operation.
It first runs a
SELECT, and if there are no results an
INSERT is attempted.
In high scale applications,
this can cause race conditions due to stale reads.
Separate threads might attempt to first
and then end up
INSERT ing multiple records.
Overcoming duplicate inserts
One way of still overcoming this race condition is catching duplicate record errors. These errors are thrown only if there is an underlying unique constraint on a field.
In the above scenario, it attempts an insert,
if a race condition is hit,
ActiveRecord::RecordNotUnique is thrown.
We can simply rescue and retry again,
to fetch the existing record.
create_or_find_by tries to create a new record
with the given attributes,
that has a unique constraint on one or several of its columns.
As in our example above, if a record already exists with one of these unique constraints, an exception raised is first caught.
It then proceeds to use
find_by! and returns the record.
This helps users to overcome the stale reads issue which is caused by race conditions.
create_or_find_by does have its limitations even though it overcomes stale reads issue.
We can still be hit by a race condition of
which will end up with no result found.
This is a much rarer scenario than the
This only works if we have unique constraints on all columns we are using
to create or find by.
If we don’t, it will not raise
ActiveRecord::RecordNotUnique, and simply insert a duplicate record.
Since all this mechanism relies on throwing and catching exceptions, it can tend to be relatively slower.