Rails Reloader: A Lesser Known Railtie Hook
I recently wrote a book about integrating with Rails from a Ruby gem, which specifically touched on using a Railtie to extend
ActionView . While these are the 3 more popular Rails libraries, there’s plenty others that are configurable.
A recent issue in Sucker Punch caused me to go digging through the Rails source code. Ultimately, the
to_prepare method on
ActionDispatch::Reloader resolved the issue, but I surprised was to find very little documentation about it.
Sucker Punch lazily creates the Celluloid pools used for background job queues. For the purpose of keeping track of the queues already initialized, Sucker Punch makes use of the Celluloid Registry. Think of it as a class-level hash.
This works swimmingly in production, but not so much in development. Rails makes our lives easier by reloading code in between requests while in development, due to this setting in
Without it, we’d be forced to restart the server after almost every request. If that sounds like a giant PITA to you, I whole heartedly agree!
So now you make your awesome job class, do some background work (send an email for example) and reload the page and boom:
The Celluloid registry still has reference to a the original
SendInvitationJob class when it was initialized, however, reloading the code has caused the original reference to disappear and all hell breaks loose when the queue key is fetched to send another job to the class.
In my head, it made sense for the queues to be cleared out upon every request in development. In general, because Sucker Punch doesn’t have persistent queues, the best use case is for quick one-off jobs that aren’t extremely important — email and logging come to mind. Since both of these examples are typically pretty speedy, it’s unlikely there will be a huge job backup upon subsequent requests.
I knew what I wanted, but didn’t know how to accomplish it.
Knowing the issue was related to the setting
config.cache_classes = false in the development environment, I broke open the Rails source code and searched for
cache_classes. The first result was the
ActionDispatch reloader middleware. Fortunately, there’s a very descriptive comment at the top of the class:
This functionality is exactly what I needed!. From here, I just needed to know what callbacks were valid. A few lines in to the class are the following methods:
to_cleanup…and like the comments say, they do exactly what you’d expect. Given that I wanted to clear our the Celluloid registry BEFORE each request,
on_prepare is the golden ticket. Now I just needed to figure out how to clear the registry.
A quick glade over the
Celluloid::Registry class documentation shows some methods that might be of value. It turns out that these are instance methods for an instance of the
Celluloid::Registry class. Unfortunately, when Celluloid boots, it instantiates a registry to use internally, so we need a way to get at that particular instance and clear it out. Sure enough, a class method to do just that in
Celluloid::Actor is available.
Now that we all the pieces of the puzzle, it was time to put together a Railtie to trigger the behavior. Prior to needing this functionality, the Railtie in Sucker Punch was pretty simple:
All it did was connect the logger to the existing Rails logger. Adding the callback to
ActionDispatch looks like:
Now when the Railtie is loaded, the
Celluloid::Actor.clear_registry method is triggered before the reloading of code in the development environment, clearing out the Celluloid registry and allowing Sucker Punch to instantiate new job queues for each request.
I was unaware of any of these methods when the issue was submitted. Rather than throw my hands up and close the issue because it didn’t affect me, I thought through an approach that could work, and only then started to write code. And in fact, didn’t know what code to write!
Comments and well written code serve as great documentation. I probably wouldn’t have stumbled on
ActionDispatch::Reloader without the detailed comments at the top of the class. Sure, I would’ve found the
cache_classes line, but might not have given it more thought.
Next time you have a question about the syntax of a method or the order of its arguments, clone the repo (if it’s open source, of course) and do a search. I think you’ll be surprised at how quickly you can find what you’re looking for. My guess is you’ll also be pleasantly surprised at the other things you stumble upon in the process.