I’ve often wanted to use ActiveRecord::Enum for the features it provides like query methods and value restrictions — but wanted to map to strings in the database, rather than integers. Whatever hypothetical performance or storage advantage you get from using integers, I’d rather trade it for more easily human-readable database values, and avoiding the Enum ‘gotcha’ where db values depend on the order the enumerated values are listed in your model in the basic Enum usage case.
So I took a look at the source to see how hard it would be to add — and it looked like it wouldn’t be hard at all, it looked kind of like it should work already! So okay, let’s add some tests and see if there are any problems. Find the test file in master — and hey, look at that, it’s already sets up a test model with string values in master, and tests em.
The tests were added in this commit from (rails committer?) sgrif, which is in Rails 5.0.0.beta2 and on. The commit message suggests it’s fixing a bug, not adding a new feature, and indeed only adds 7 new lines to enum.rb source.
It says it ‘fixes’ a (not directly merged) Pull Request #23190, which itself says it fixes Issue #23187 , with all of this code reverting each other and other commits. Which all have pretty terse comments, but seem to be about db-defined default values in Enums, rather than String vs Integer. But if they were only meant to solve that problem, why did @sgrif add tests for string values here?
So maybe Enum has supported String mappings for quite a while? But maybe buggily and without tests? Or was it a feature added in Rails 5, but buggy until 5.0.0.beta2? I actually tried it out in Rails 4.2.7, and it seemed to work in SQLite3. Including with db default values. But maybe with edge case bugs in at least some db adapters, that are fixed in Rails 5?
It’s not in the docs at all.
In my opinion, committing new code without updating docs to match is just as much a sin as committing new code without tests. I’m not sure if everyone (or Rails core) shares my opinion. But it’s not like this was an out of the way doc reference that someone wouldn’t have noticed, it’s the module header docs themselves which are missing a reference to non-integer values! But maybe the committer considered this a bugfix rather than a new feature so didn’t think they needed to consider doc updates. Hard to say. The mysteries of Rails!
In any event, it seems safe to use Enum with String values in Rails 5 — there are test cases and everything. And likely will even work in Rails 4.2.7, although there are no test cases in Rails 4.2.x and there may be edge case bugs I didn’t run into? The docs should really be updated. If you’re looking to have your name in the commit logs for Rails, maybe you want to update the docs? Or maybe I’ll get around to it.
update Sep 7
So, interestingly, this also seems to work just fine with a database column that’s been defined as a Postgres Enumerated Type. The Rails PostgreSQL Guide says: “Currently there is no special support for enumerated types. They are mapped as normal text columns.”
However, in fact, since ActiveRecord maps Postgres enumerated types as string/text, so long as ActiveRecord::Enum supports string/text values as it seems to, it looks like you can use ActiveRecord::Enum as a cover for it on the Rails side, getting restricted values (on the Rails side), query methods, automatic scopes, etc. Which is pretty nice. I tested this on Rails 4.2.7 and 5.0 and was unable to find any problems.
However, the bad news is that Rails team has said in response to a doc PR “This is left out of the documentation because while it happens to work, this isn’t something that we want to explicitly support, and reserve the right to break in the future.”
So that is what it is. This is a weird situation, because it doesn’t make sense to write a third-party plugin for Rails to add this feature — it wouldn’t include any code at all, because the feature already works just fine. (I suppose I could copy and paste the Enum implementation into the plugin and give it different names? That seems silly though). It’s there, it works fine — but may go away in the future, so use at your own risk.
I gotta say I’m mystified by Rails decision-making approach these days. They throw in a variety of 0fficially supported features that nobody but dhh understands any use cases for (thread_mattr_accessor anyone?), but a feature that lots of people have asked for, that is actually already present and working with test cases — they say they don’t want to explicitly support. (And it’s not like something being doc’d and ‘supported’ usually stops Rails from removing it in the next version before anyway!). Go figure. I assume if dhh ever ran into the cases we have where he wanted this, it would magically become ‘explicitly supported’.
Hmm, maybe I will just copy the enum.rb implementation into my own gem, I dunno.