Generally, global variables are bad. But sometimes, a global in the right place can make your code a lot simpler.
In Rails, you might set data once during a request, but use it in every layer of your app. Which user is making the request? What permissions do they have? Which database connection should be used?
This information should be available throughout your code, so passing it everywhere would just be a bunch of noise. But using a Ruby global variable, or setting a class variable, will cause its own problems. Multiple threads could overwrite it, and you’d end up with a huge mess, and maybe unrecoverably bad data.
You need something that’s global, but for just your request.
The plain Ruby way
Usually, you’ll see this handled with
Thread.current. It looks like this:
This is simple enough. It’s a decent solution. But it has two big drawbacks:
Someone could accidentally overwrite your data.
What if two people picked the same key? You’d stomp on each other’s data. And if you’re storing something like users, that would be seriously bad. In your own apps, this probably won’t be a problem. But if you’re writing gems, or your Rails app is big and messy, it’s something you need to think about.
It’s not well-structured.
Thread.currentis just a big bag of data. You can’t easily document it. If you want to know what you can take out of it, you have to search for what you put into it.
Of course, if you’re putting away enough global data that this is a problem, you have more to worry about than
Thread.current’s lack of structure. But it’s still a point to keep in mind.
So, is there a better solution than
Thread.current? As you might imagine, Rails itself manages a lot of request-local data. And the box of goodies that is ActiveSupport has a different pattern to follow.
The Rails way
If you dig through ActiveSupport, you might run into
ActiveSupport::PerThreadRegistry. You can probably tell from the name that this is exactly what we’re looking for.
ActiveSupport::PerThreadRegistry, the earlier example would look like this:
1 2 3 4 5 6 7 8
It’s a tiny bit more work. But with
You have a place to put documentation. Anyone looking at your class will know exactly what global data you’re expecting, and what global data they can use.
You get an API that looks good. In fact, it looks like you’re just setting class variables. That’s probably what you’d be doing if you didn’t have to worry about threads.
Any data you set on the class will be namespaced. You don’t have to worry about
PhoneNumberApiRegistry.current_user, they’re treated totally separately.
ActiveSupport::PerThreadRegistry internally, for things like ActiveRecord connection handling, storing EXPLAIN queries, and ActiveSupport::Notifications. So if you’re looking for more good examples of the power of
PerThreadRegistry, Rails itself is a great place to start.
When threads and requests don’t match
For some Rails servers, a single thread handles multiple requests. This means anything you put into a
PerThreadRegistry will stick around for the next request. This is probably not what you want.
You could do what Rails does, and clean up the things you put into the
PerThreadRegistry after you’re done with them. Depending on what you put in there, you might be doing this anyway.
This leaves the door wide open for someone to create a
PerRequestRegistry, which combines the safety of request_store with the API of
PerThreadRegistry. If someone’s done this already, I’d love to hear about it!
Try it out
Global data can be bad. Too many globals can quickly lead to unmaintainable code.
But if it makes sense to keep data in a central place for an entire request, go beyond
ActiveSupport::PerThreadRegistry or request_store a shot. If nothing else, it’ll make dealing with global data a little less risky.