taylors_enum
taylors_enum is a gem that builds on top of ActiveRecord's built in enums. Specifically, it will:
- Define additional methods to make it clearer which values exist in Rails-land, and which in the database
In Rails:
class Album < ActiveRecord::Base
self.table_name = 'albums'
enum name: %w[debut fearless speak_now red nineteen_eighty_nine reputation lover folklore evermore]
end
$ Album.names
=> {"debut"=>0, "fearless"=>1, "speak_now"=>2, "red"=>3, "nineteen_eighty_nine"=>4, "reputation"=>5, "lover"=>6, "folklore"=>7, "evermore"=>8}
With taylors_enum:
class AlbumBase < Album
taylors_enum name: %w[debut fearless speak_now red nineteen_eighty_nine reputation lover folklore evermore]
end
$ AlbumBase.name_rails_values
=> ["debut", "fearless", "speak_now", "red", "nineteen_eighty_nine", "reputation", "lover", "folklore", "evermore"]
$ AlbumBase.name_database_values
=> ["debut", "fearless", "speak_now", "red", "nineteen_eighty_nine", "reputation", "lover", "folklore", "evermore"]
- Define constants for each value provided
class AlbumBase < Album
taylors_enum name: %w[debut fearless speak_now red nineteen_eighty_nine reputation lover folklore evermore]
end
$ AlbumBase::FOLKLORE
=> "folklore"
$ AlbumBase::EVERMORE
=> "evermore"
# etc.
- Enables support for all the enum goodness when enumerating classes for Single Table Inheritance
module SingleTableInheritanceAlbums
class SingleTableInheritanceAlbum < ActiveRecord::Base
self.table_name = 'single_table_inheritance_albums'
taylors_enum type: %w[
SingleTableInheritanceAlbums::Debut
SingleTableInheritanceAlbums::Fearless
SingleTableInheritanceAlbums::SpeakNow
SingleTableInheritanceAlbums::Red
SingleTableInheritanceAlbums::NineteenEightyNine
SingleTableInheritanceAlbums::Reputation
SingleTableInheritanceAlbums::Lover
SingleTableInheritanceAlbums::Folklore
SingleTableInheritanceAlbums::Evermore
], single_table_inheritance: true
end
class Debut < SingleTableInheritanceAlbum; end
class Fearless < SingleTableInheritanceAlbum; end
class SpeakNow < SingleTableInheritanceAlbum; end
class Red < SingleTableInheritanceAlbum; end
class NineteenEightyNine < SingleTableInheritanceAlbum; end
class Reputation < SingleTableInheritanceAlbum; end
class Lover < SingleTableInheritanceAlbum; end
class Folklore < SingleTableInheritanceAlbum; end
class Evermore < SingleTableInheritanceAlbum; end
end
$ album = ::SingleTableInheritanceAlbums::Debut.create
=> #<SingleTableInheritanceAlbums::Debut:0x000000010581a708 id: 2, type: "SingleTableInheritanceAlbums::Debut", created_at: 2022-06-04 14:34:24.951932 UTC, updated_at: 2022-06-04 14:34:24.951932 UTC>
$ album.type
=> "SingleTableInheritanceAlbums::Debut"
$ album.debut?
=> true
$ album.fearless!
=> true
$ album.debut?
=> false
$ album.fearless?
=> true
$ album.type
=> "SingleTableInheritanceAlbums::Fearless"
$ ::SingleTableInheritanceAlbums::SingleTableInheritanceAlbum.fearless
=> [#<SingleTableInheritanceAlbums::Fearless:0x00000001059821e0 id: 1, type: "SingleTableInheritanceAlbums::Fearless", created_at: 2022-06-04 14:33:55.178418 UTC, updated_at: 2022-06-04 14:33:55.178418 UTC>,
#<SingleTableInheritanceAlbums::Fearless:0x0000000105981a10 id: 2, type: "SingleTableInheritanceAlbums::Fearless", created_at: 2022-06-04 14:34:24.951932 UTC, updated_at: 2022-06-04 14:34:50.616316 UTC>]
$ ::SingleTableInheritanceAlbums::SingleTableInheritanceAlbum.fearless.to_sql
=> "SELECT \"single_table_inheritance_albums\".* FROM \"single_table_inheritance_albums\" WHERE \"single_table_inheritance_albums\".\"type\" = 'SingleTableInheritanceAlbums::Fearless'"
- Enables support for all the enum goodness when enumerating classes for Polymorphic Associations
Note: this excludes the <attribute>!
method to update a value, as, as I write this, I can't conceive of a scenario in which you'd want to update just the type, and not also the ID, of an associated object. That doesn't mean you won't ever want to! And if you do, you can still do so manually via the ActiveRecord#update
method.
class Award < ActiveRecord::Base
self.table_name = 'awards'
belongs_to :awardable, polymorphic: true
taylors_enum awardable_type: %w[Album Song], polymorphic: true
end
$ song = Song.create!
=> #<Song:0x0000000105beddd0 id: 1, name: nil, created_at: 2022-06-04 14:39:38.837973 UTC, updated_at: 2022-06-04 14:39:38.837973 UTC>
$ album = AlbumBase.create!(name: :folklore)
=> #<AlbumBase:0x0000000105c36c10 id: 1, name: "folklore", created_at: 2022-06-04 14:39:43.001592 UTC, updated_at: 2022-06-04 14:39:43.001592 UTC>
$ song_award = Award.create!(awardable: song)
=> #<Award:0x0000000105c77030 id: 1, awardable_type: "Song", awardable_id: "1", created_at: 2022-06-04 14:39:46.714235 UTC, updated_at: 2022-06-04 14:39:46.714235 UTC>
$ album_award = Award.create!(awardable: album)
=> #<Award:0x0000000105cb4ac0 id: 2, awardable_type: "Album", awardable_id: "1", created_at: 2022-06-04 14:39:49.444071 UTC, updated_at: 2022-06-04 14:39:49.444071 UTC>
$ song_award.song?
=> true
$ song_award.album?
=> false
$ album_award.song?
=> false
$ album_award.album?
=> true
$ Award.song
=> [#<Award:0x0000000105dbfca8 id: 1, awardable_type: "Song", awardable_id: "1", created_at: 2022-06-04 14:39:46.714235 UTC, updated_at: 2022-06-04 14:39:46.714235 UTC>]
$ Award.song.to_sql
=> "SELECT \"awards\".* FROM \"awards\" WHERE \"awards\".\"awardable_type\" = 'Song'"
Installation
Install the gem and add to the application's Gemfile by running:
$ bundle add taylors_enum
If bundler is not being used to manage dependencies, install the gem by running:
$ gem install taylors_enum
Usage
Once the gem is installed, add taylors_enum <my_column>: ['array', 'of', 'values'], **options
to any model that ultimately inherits from ActiveRecord::Base
.
The values that you pass will be the values that are stored in the database - except when passing integer: true
(see below) in the options; in this case, pass an array of the values you expect to see in the Rails application. taylors_enum will then generate a companion for each value that will be used to define constants, ?
methods to check if an object has the given value in the specified column, !
methods to update the column to that value, and scopes to query for records with that value. See the top of this README for what this looks like in practice.
taylors_enum also takes a series of options, provided as a hash following the specified values:
prefix
: defaults tonil
. Iftrue
is passed, the name of the column will be prepended to the start of the helper methods, scopes, and constants. If a string is passed, the given string will be prepended to the start of the helper methods, scopes, and constants.suffix
: defaults tonil
. Iftrue
is passed, the name of the column will be appended to the end of the helper methods, scopes, and constants. If a string is passed, the given string will be appended to the end of the helper methods, scopes, and constants.constants
: defaults totrue
. Iffalse
is passed, constants will not be defined. This can be helpful for migrating onto taylors_enum, if you already have constants defined for certain enum values.validations
: defaults totrue
. Iffalse
is passed, validations,nil
will be allowed as a value in the specified column.single_table_inheritance
: defaults tofalse
. When using taylors_enum to help with columns on Single Table Inheritance models, pass this value astrue
, otherwise things won't work, and then you'll be sad. Note: this cannot be passed astrue
ifinteger
orpolymorphic
are both specified astrue
; this will raise an OptionsConflictError when loading the application.polymorphic
: defaults tofalse
. When using taylors_enum to help with _type column for polymorphic associations, pass this value astrue
; this will ensure validations are run correctly, against database values rather than Rails values, and will also not create a!
method to update the value of the column. Note: this cannot be passed astrue
ifsingle_table_inheritance
orinteger
are both specified astrue
; this will raise an OptionsConflictError when loading the application.integer
: defaults tofalse
. When using taylors_enum with an integer column rather than a string column, pass this astrue
to ensure constants, scopes, and helper methods are defined appropriately. Should this be something that can be inferred, rather than needing to be passed explicitly? Absolutely. However, at the time of writing, I can't figure out how to get access to this information at the point that this code is called (in theActiveSupport::LazyLoadHooks.on_load
callback), so this fudge allows integer columns to function as expected for now. Note: this cannot be passed astrue
ifsingle_table_inheritance
orpolymorphic
are both specified astrue
; this will raise an OptionsConflictError when loading the application.
If you want to see the base Rails value that will be used to generate the value?
and value!
methods, that VALUE
constants, and the value
scopes, you can load up a Rails console with rails c
, and run MyModel.check_rails_value_for(database_value)
. For example:
class AlbumBase < Album
taylors_enum name: %w[debut fearless speak_now red nineteen_eighty_nine reputation lover folklore evermore]
end
$ AlbumBase.check_rails_value_for('folklore')
=> 'folklore'
$ AlbumBase.check_rails_value_for('folklore', column: :name, prefix: true)
=> 'name_folklore'
Development
After checking out the repo, run bin/setup
to install dependencies. Then, run rake test
to run the tests. You can also run bin/console
for an interactive prompt that will allow you to experiment.
You'll also need to have a working database in order to run the tests. First, make sure you have Postgres installed. You can easily do this on a Mac with Homebrew.
From there, run createdb taylors_enum
in the terminal to create a database with the required name. Then, run psql taylors_enum
to open a terminal to that database, and run create user postgres;
to create a user with the correct username for the test suite to connect to it. The database connection config is specified in test/database.yml
To install this gem onto your local machine, run bundle exec rake install
. To release a new version, update the version number in version.rb
, and then run bundle exec rake release
, which will create a git tag for the version, push git commits and the created tag, and push the .gem
file to rubygems.org.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/isitpj/taylors_enum.
License
The gem is available as open source under the terms of the MIT License.