Rails 6 has added ActiveRecord::Base.create_or_find_by/! as an alternative to ActiveRecord::Base.find_or_create_by/!
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 SELECT
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,
an ActiveRecord::RecordNotUnique
is thrown.
We can simply rescue and retry again,
to fetch the existing record.
create_or_find_by
Enter create_or_find_by.
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.
Limitations
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 INSERT
-> DELETE
-> SELECT
,
which will end up with no result found.
This is a much rarer scenario than the SELECT
-> INSERT
situation.
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.