Flags are everywhere. These boolean fields are so common that I cannot imagine a mature application that wouldn’t use them in multiple models – from the ubiquitous “admin” column on a User model to the “cancelled” flag on a Reservation model to various temporary flags that determine who should have access to some new features being tested. I’ve worked with apps that had very extensive models with many columns, out of which even 6 or 7 of were just booleans of various kinds.
I like order, consistency, and readability – imagine a project where flags are (quite randomly) mixed with other columns in the schema file. Inspecting objects in the console becomes a headache. Not a huge one, but still a headache. Legacy projects may have old flags that clutter the database and whose purpose nobody remembers. But my biggest issue are lots and lots of migrations that added new and removed unused feature flags throughout the history of the project. And whenever you want to add a small, temporary flag for something, you need to go through the whole process of writing and executing a migration, plus another migration when you’re finished with this flag, of course assuming that you remembered to remove it. Want to change the name of a certain flag, perhaps to reuse it? Same story.
I recently got assigned to a project where I worked shoulder-to-shoulder with the client’s in-house team of developers, and they introduced me to some really good patterns and practices that I hadn’t been aware of. One of those practices was to use the Flag Shih Tzu gem instead of traditional flags. The name might sound silly, and the idea might seem weird or scary at first, but trust me – it has a great potential and will change the way you look at flags in your Rails apps.
The idea is quite simple: the model gets ONE new integer column which will contain the information about the values of all your current and potential future flags without doing any migrations that add or remove these flags later on. You name your flags inside the model. You can freely add, remove, and rename these flags at any time without a single migration. The gem gives you lots of useful, dynamically created class and instance methods, as well as scopes for quickly finding what you’re looking for. On top of that, this single column can be indexed, potentially giving you even more performance compared to simple boolean columns.
Using Flag Shih Tzu is actually pretty easy – after adding the gem to your Gemfile you just insert the `flags` integer field into the desired table (not allowing a null value, defaulting to 0) and include FlagShihTzu in the model. From now on, you can freely define and change flags in your model using the following format:
has_flags 1 => :admin,
2 => :onboarded,
3 => :can_use_mailer
All aboard the flag train! That’s it!
It’s important to remember that keys in this hash have to be positive integers that will map to the position of that flag.
After that we gain access to scopes such as `admin` and `not_admin`, we can ask class instances about the values, e.g. `User.find(5).admin?`, change the value using `#update` and do most of the stuff that ActiveRecord allows us to do normally.
The only apparent difference is that you’d have to approach some queries differently – you won’t be able to do such things as `where(onboarded: true)` or `find_by(can_use_mailer: false)`. Instead, you’d first scope these queries to particular flag values and then find specific objects through other attributes. Also, having dozens of such flags can cause a slight lag when generating possible integer values – but having 20 or more boolean flags on one model might not have been a great idea in the first place.
Boolean flags are an inseparable element of most Rails apps. They control various aspects of the model business logic – from giving particular user permissions to hiding certain unfinished features. They may outgrow your initial assumptions, which is why you should consider using the FlagShihTzu gem and making your flags much more manageable without all the mess associated with migrations and inserting/removing columns in the database.