DEV Community

Augusts Bautra
Augusts Bautra

Posted on

2

Simple way to deal with race conditions in Rails

tl;dr

Wrap all .first_or_create!/find_or_create_by! calls in retryable block.

Retryable.retryable(
  on: [ActiveRecord::RecordNotUnique, ActiveRecord::RecordInvalid]
) do .. end
Enter fullscreen mode Exit fullscreen mode

Discussion

Oftentimes in Rails apps we have a situation where only one of something is allowed. A user may be allowed only one profile picture, a question only one answer, only one pinned post etc.

In these situations I prefer using the "ensure" pattern with .first_or_create!/find_or_create_by!. There's a gotcha, however. Both of these methods are vulnerable to race conditions, especially if the ensuring logic is called in GET action context (though POSTs that do not disable to submit button may also suffer from this).

What happens is several near-parallel requests reach the .first_or_create!, and the check for any existing records returns false for both, so both proceed to creation, but only one will succeed in this, and the other will fail.

While we should strive to reduce unnecessary requests by disabling submit buttons after click etc., oftentimes the source of requests is not under our control - modern browsers tend to make pre-fetch requests etc., so handling the race condition is a must. Luckily this is easy.

I like retryable gem a lot for this, it has a clean, powerful DSL.

# in Gemfile
gem "retryable" # plus version lock
Enter fullscreen mode Exit fullscreen mode

Then wrap any .first_or_create!/find_or_create_by! call in a retrying block. By default it will retry just once and wait one second after a failure if there is one, you can tweak all that.

# before
user.filters.where(filter_name: self.class.name, context: context).first_or_create!

# after
Retryable.retryable(
  on: [ActiveRecord::RecordNotUnique, ActiveRecord::RecordInvalid]
) do
  user.filters.where(filter_name: self.class.name, context: context).first_or_create!
end
Enter fullscreen mode Exit fullscreen mode

Postmark Image

The email service that speaks your language

Whether you code in Ruby, PHP, Python, C#, or Rails, Postmark's robust API libraries make integration a breeze. Plus, bootstrapping your startup? Get 20% off your first three months!

Start free

Top comments (0)

ACI image

ACI.dev: The Only MCP Server Your AI Agents Need

ACI.dev’s open-source tool-use platform and Unified MCP Server turns 600+ functions into two simple MCP tools on one server—search and execute. Comes with multi-tenant auth and natural-language permission scopes. 100% open-source under Apache 2.0.

Star our GitHub!

👋 Kindness is contagious

Explore a trove of insights in this engaging article, celebrated within our welcoming DEV Community. Developers from every background are invited to join and enhance our shared wisdom.

A genuine "thank you" can truly uplift someone’s day. Feel free to express your gratitude in the comments below!

On DEV, our collective exchange of knowledge lightens the road ahead and strengthens our community bonds. Found something valuable here? A small thank you to the author can make a big difference.

Okay