When Create Does Not Create in Ruby on Rails, Wait for Transaction!
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.
So, is after_commit
the solution?
Not really, since we want something that gets called only after one specific create
and can access our worker_params
.
Unfortunately, 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!
Summary
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!