Table-based enumerations for ActiveRecord.
Deprecation Notice
ActiveRecord 4.1 introduced its own way of handling enumerations, which is somewhat at odds with the approach of this gem. To get the additional benefits of Enum Table, it's suggested that you migrate to standard ActiveRecord enums, and then sync the enum values in your code to your database as necessary.
Enum Table is compatible with ActiveRecord versions up to 4.1.x, but 4.2.x support is not currently planned.
What?
When you have a column that should only take one of a finite set of string values (e.g., gender, statuses through some workflow), it's usually best not to store these as strings. Many databases, such as MySQL and PostgreSQL have native enum types which effectively let you treat these as strings while storing them internally using as few bytes as possible. Indeed there are already plugins that let you use these native enum types.
But sometimes this is inadequate.
Most obviously, not all databases have a native enum type, notably SQLite.
Further, in the case of MySQL, the enum type leaves a lot to be desired in a production setting. If you need to do anything to the list of allowed values other than adding values to the end of the list, MySQL will rebuild the entire table, which can be very slow. Unless you're using something like pt-online-schema-change, it will also lock the table during this period, which could be unacceptable for large tables.
A common alternative is to simply keep the value-to-id mapping in the application, hardcoded in the model. The downside of this is that your database is no longer self-documenting: the integers mean nothing without the value mapping buried in your application code, making it hard to work with the database directly. Another problem is the database cannot enforce any referential integrity.
This plugin implements a different strategy which solves the above problems -
each enum is defined by a table with id
and value
columns, which defines the
values and the integers they map to. Altering the values can be done with simple
DML statements, which do not require rebuilding any tables.
Usage
Create your enum tables in migrations. Example:
create_enum_table :user_genders do |t|
t.add :male
t.add :female
end
Then add the enum ID to your model table:
add_column :users, :gender_id, null: false
Then in your model:
class User < ActiveRecord::Base
enum :gender
end
Note the convention: for a model User
with enum gender
, the column is
users.gender_id
, and the enum_table is user_genders
. You can override these
with the :id_name
and :table
options:
enum :gender, id_name: :sex_id, table: :sexes
Custom columns
While the names id
and value
are fixed, you can change other attributes of
the column. For example, the ID has limit: 1
by default, but you can change
this if you have a large list of enum values:
create_enum_table :user_countries do |t|
t.id limit: 2
t.add 'Afghanistan'
t.add 'Albania'
# ...
end
Similarly you can customize the value
column, say if you want to place a
varchar limit:
create_enum_table :user_countries do |t|
t.value limit: 100
# ...
end
Updating enums
To change the list of enums:
change_enum_table :user_genders do |t|
t.add :other
t.remove :male
end
To drop an enum table:
drop_enum_table :user_genders
Under the hood, create_enum_table
and drop_enum_table
maintain the list of
enum tables in the enum_tables
table. This allows the table data to be tracked
by db/schema.rb
so it gets copied to your test database.
Hardcoded mappings
If you really want, you can forego the table completely and just hardcode the ids and values in your model:
enum :genders, table: {male: 1, female: 2}
Or since our IDs are 1-based and sequential:
enum :genders, table: [:male, :female]
Of course, by not using tables, you lose some of the advantages mentioned earlier, namely a self-documenting database and referential integrity.
Values
By default, user.gender
will be either the symbol :male
or :female
. If
you're transitioning from using old-fashioned varchar
s, however, you may find
it less disruptive to use strings instead. Do that with the :type
option:
enum :genders, type: :string
I can't set up my Rails app!
If you load any models during Rails' initialization phase (e.g. from
config/application.rb
, or an initializer), and these models depend on the
existence of enum tables, you may wind up with a circular dependency: your app
startup depends on an enum table existing, and creating the enum table requires
loading the app (via db:schema:load
). This might only manifest itself when you
try to load your schema on an empty database.
It is strongly recommended to avoid loading models during initialization. However if you cannot avoid this there is an escape hatch:
config.enum_tables.allow_missing_tables = true
This will silently return an empty enum mapping while this flag is true. Of course, this may have knock on effects if your model expects certain values to be present. Consider yourself warned!
Contributing
- Bug reports
- Source
- Patches: Fork on Github, send pull request.
- Include tests where practical.
- Leave the version alone, or bump it in a separate commit.
Running tests
testrb test
Copyright
Copyright (c) HowAboutWe. See LICENSE for details.