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 ActiveRecord
, ActionController
and 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.
The Problem
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 config/environments/development.rb
:
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.
The Solution
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_prepare
and 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.
Summary
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.