We're used to things being intuitive in the world of Ruby. When we have a method called create, we assume that things will be created after the method returns. It turns out that this method's name is a bit misleading. If you find yourself in a situation where you expect a record to be immediately created in a database after create returns, you might end up spending a lot of time trying to figure out why this doesn't happen. Or you can just read the explanation below.
Look at this piece of code:
Here we create an object of the GeneratedReport class and pass its id to some worker (in our case it's 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 does it do that:
We'll stop right here because we have an answer right in a 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. It's explained in the Ruby on Rails Guides 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” are, in our case, 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!
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!