I was working on a project recently where I had already scaffolded out a new Rails app with
rails new and had begun committing a good chunk of application code to it. The app was small, but processes lots of data in real time and so had some special considerations around data retention and throughput. After a few weeks, I found out the requirements had changed, and the assumptions I had made at the beginning of the project no longer held.
Rather than sticking with a full Rails stack, I was asked if we could run the code transiently, essentially as a standalone script, without needing a database or persistence layer.
It took some experimentation, but I can say now that I understand Rails a lot better as a framework, and it’s not very hard to get a lot of what Rails offers even in simple scripts.
How It Worked Out
This effort was my organization’s first foray into deploying a pure Ruby script to production. We typically work within Rails and have an entire ecosystem of supporting internal gems that we can quickly insert to get the functionality we need.
To that end, I wanted to make sure the script I was architecting felt “Railsy” enough for new developers to the project to be able to drop right in. A few aspects were critical, which I’ve listed below and how they turned out.
Rails (currently) uses the
zeitwerk gem to autoload all your classes.
If you’ve worked in a Rails project before and remember back to it’s “convention over configuration” motto, a lot of what Rails offers is due to this gem. For instance, need a new
Thing model backed by ActiveRecord for your
things table? Plop it into
app/models/thing.rb, define the class as
class Thing < ApplicationRecord, and
zeitwerk will know where to find it and lazy load it when it’s needed.
You can put this into multi-file Ruby applications as well – it’s very simple. You just tell it which files to load for you, and you’re off! It can even handle code reloading like the
reload! command in a Rails console.
loader = Zeitwerk::Loader.new
loader.enable_reloading # you need to opt-in before setup
One thing we use a lot is environment-specific code, for one reason or another. Rails makes this super easy to check an environment by calling
Only want your code to run in prod? Put it behind a conditional!
Implementing this in a pure Ruby script isn’t exactly hard – Ruby is just checking for the presence of
ENV["RAILS_ENV"] with the name of the environment you’re querying. To make it work as nicely as Rails… That’s a bit different.
I defined my own
Application class, with an
Environment subclass (similar to Rails’
That class is gleefully weird – it inherits from
StringInquirer, and it dynamically adds all the environment query methods. My implementation was not quite as involved, but it allowed for something like:
# Rails version
# My version
One of the key features of Rails is its DSL for working with data.
It’s actually multiple DSLs housed inside the
rails meta-gem which are meant to be cherry-picked for only what you need. For instance, if you want to work with models and data validations just like you would in full Rails, you can just install
activemodel and get that for free. Like the date/time helpers you get in an out-of-the-box Rails install? Pick
activesupport. And the list goes on.
Plus, each of these smaller pieces can be further broken down to truly specialize what you need. For me, I only needed model validations from
activemodel because I wanted it to behave like ActiveRecord objects that my coworkers are all very familiar with. In my case, I can just layer in what’s needed:
attr_accessor: :id, :name, :type, :description
thing = Thing.new(id: 1, name: "Ty", type: "something", description: "test")
(Astute readers will note that the
EnvironmentInquirer class is a part of ActiveSupport, so I should have just included it that way!)
Hopefully this sheds some light on how Rails is organized as a gem and maybe a bit of how it works under the hood. It was interesting to get to deep dive into Rails itself, and I definitely feel like I come way with a lot of knowledge when I get to do things like this.
That’s all for now. Thanks for reading!
- ← Previous: Launching Dark (Postmortem)
- Next: Thoughts on "Developers Should Deploy Their Own Code" →