Ruby on Rails is a compact, elegant, and versatile way to build web applications.This highly popular framework offers developers a vast library of features and comes with a range of benefits, like being time-efficient, cost-effective, consistent, and scalable.
It is also renowned as easy to learn, but that’s not to say that it doesn’t come without any pitfalls.
Here, we take a look at one of the lesser-known quirks of Ruby on Rails that could trip you up, and show you how to avoid it. You never know, it might just save your day!
When Create Does Not Create in Ruby on Rails, Wait for Transaction!
We're used to things being intuitive in the world of Ruby. So when we’re dealing with a method called
create, we might naturally assume that something will be ‘created’ after the method returns.
As it turns out, this method's name is a bit misleading. If you find yourself in a situation where you expect a database record to immediately be created after
create returns, you might waste a lot of time trying to figure out why this hasn’t happened. Allow me to explain:
Take this piece of code for example:
Here we create an object of the
GeneratedReport class and pass its id to some worker (in this case it's a Sidekiq worker). There shouldn't be any problems here, right? Well, when the worker tries to get the object using its id this happens:
So what's going on here? When we look at what
create actually does, we see:
It definitely saves the object, so let's now look into how it does that:
We'll stop here because we have a clue right in the method name: transaction!
If you want things to appear in a database, you have to wait for a transaction to be successfully committed. That's why you need to wait for the
after_commit callback. The Ruby on Rails Guides explains it as follows:
"There are two additional callbacks that are triggered by the completion of a database transaction: after_commit and after_rollback. These callbacks are very similar to the after_save callback except that they don't execute until after database changes have either been committed or rolled back. They are most useful when your active record models need to interact with external systems which are not part of the database transaction."
Those “external systems” in our case, are Sidekiq workers.
And this approach makes sense if you think about it.
Most of the time you'll be interacting with an object in memory, so you don't really care that it doesn't exist in your database yet. That's why there’s usually no point in waiting for a transaction to end.
after_commit the solution?
Not really, since we want something that gets called only after one specific
create and can access our
after_commit doesn’t meet those requirements. That's why we have to use this brilliant little gem called ar_after_transaction. Now, our initial code will look like:
ActiveRecord::Base.after_transaction checks whether a transaction is currently open. If it's not, it starts the worker immediately. If it is, it waits for it to commit and then starts the worker.
Finally, something that does exactly what it claims it does! Faith in intuitive method names restored!
Ruby on Rails is mostly highly intuitive, but it’s not without its quirks. Being aware of them before you start your project will allow you to save time in the long-run, keep costs down, and avoid any problems with your app crashing.
The code above will run flawlessly, without any exceptions. Just remember that using
ar_after_transaction doesn't make sense most of the time, but it saves the day when you need to get some recently created object directly from a database!