A Rails plugin which brings easy-to-use enum-like functionality to ActiveRecord and Mongoid models (now compatible with rails 3.1, ruby 1.9 and jruby).
Since version 1.4, simple_enum is no longer compatible with activerecord 2.x, use version 1.3.2 instead: <github.com/lwe/simple_enum/tree/v1.3.2>.
For Mongoid support, you need to use the master branch as of Jan 28, 2012 as earlier branches do not give you SimpleEnum::Mongoid. Also, you need to include this gem after you include Mongoid in GemFile. Otherwise, code for supporting Mongoid will not run.
Note: a recent search on github for ‘enum` turned out, that there are many, many similar solutions.
Add this to a model:
class User < ActiveRecord::Base as_enum :gender, :female => 1, :male => 0 end
Then create the required ‘gender_cd` column using migrations:
class AddGenderColumnToUser < ActiveRecord::Migration def self.up add_column :users, :gender_cd, :integer end def self.down remove_column :users, :gender_cd end end
Add this to a model:
class User include Mongoid::Document include SimpleEnum::Mongoid as_enum :gender, :female => 1, :male => 0 end
Done. Now it’s possible to pull some neat tricks on the new column, yet the original db column (gender_cd
) is still intact and not touched by any fancy metaclass or similar.
jane = User.new jane.gender = :female jane.female? # => true jane.male? # => false jane.gender # => :female jane.gender_cd # => 1
Easily switch to another value using the bang methods.
joe = User.new joe.male! # => :male joe.gender # => :male joe.gender_cd # => 0
There are even some neat tricks at class level, which might be useful when creating queries, displaying option elements or similar:
User.genders # => { :male => 0, :female => 1 } User.genders(:male) # => 0, same as User.male User.female # => 1 User.genders.female # => 1, same as User.female or User.genders(:female)
-
Too tired of always adding the integer values? Try:
class User < ActiveRecord::Base as_enum :status, [:deleted, :active, :disabled] # translates to :deleted => 0, :active => 1, :disabled => 2 end
Disclaimer: if you ever decide to reorder this array, beaware that any previous mapping is lost. So it’s recommended to create mappings (that might change) using hashes instead of arrays. For stuff like gender it might be probably perfectly fine to use arrays though.
-
Want to use ‘SimpleEnum` in an ActiveModel, or other class, just do:
class MyModel include SimpleEnum attr_accessor :gender_cd as_enum :gender, [:male, :female] end
-
Maybe you’ve columns named differently than the proposed
{column}_cd
naming scheme, feel free to use any column name by providing an option:class User < ActiveRecord::Base as_enum :gender, [:male, :female], :column => 'sex' end
-
Need to provide custom options for the mongoid field, or skip the automatically generated field?
# skip field generation field :gender_cd # <- create field manually (!) as_enum :gender, [:male, :female], :field => false # custom field options (directly passed to Mongoid::Document#field) as_enum :gender, [:male, :female], :field => { :type => Integer, :default => 1 }
-
To make it easier to create dropdowns with values use:
<%= select(:user, :gender, User.genders.keys) %>
-
It’s possible to validate the internal enum values, just like any other ActiveRecord validation:
class User < ActiveRecord::Base as_enum :gender, [:male, :female] validates_as_enum :gender end
All common options like
:if
,:unless
,:allow_nil
and:message
are supported, because it just works within the standardvalidates_each
-loop. This validation method does not check the value ofuser.gender
, but instead the value of@user.gender_cd
. -
If the shortcut methods (like
<symbol>?
,<symbol>!
orKlass.<symbol>
) conflict with something in your class, it’s possible to define a prefix:class User < ActiveRecord::Base as_enum :gender, [:male, :female], :prefix => true end jane = User.new :gender => :female jane.gender_female? # => true User.gender_female # => 1, this also works on the class methods
The
:prefix
option not only takes a boolean value as an argument, but instead can also be supplied a custom prefix (i.e. any string or symbol), so with:prefix => 'foo'
all shortcut methods would look like:foo_<symbol>...
Note: if the:slim => true
is defined, this option has no effect whatsoever (because no shortcut methods are generated). -
Sometimes it might be useful to disable the generation of the shortcut methods (
<symbol>?
,<symbol>!
andKlass.<symbol>
), to do so just add the option:slim => true
:class User < ActiveRecord::Base as_enum :gender, [:male, :female], :slim => true end jane = User.new :gender => :female jane.female? # => throws NoMethodError: undefined method `female?' User.male # => throws NoMethodError: undefined method `male'
Yet the setter and getter for
gender
, as well as theUser.genders
methods are still available, only all shortcut methods for each of the enumeration values are not generated.It’s also possible to set
:slim => :class
which only disables the generation of any class-level shortcut method, because those are also available via the enhanced enumeration hash:class Message < ActiveRecord::Base as_enum :status, { :unread => 0, :read => 1, :archived => 99 }, :slim => :class end msg = Message.new :body => 'Hello World!', status_cd => 0 msg.read? # => false; shortuct methods on instance are still enabled msg.status # => :unread Message.unread # => throws NoMethodError: undefined method `unread` Message.statuses.unread # => 0 Message.statuses.unread(true) # => :unread # or useful for IN queries Messages.statuses(:unread, :read) # => [0, 1]
-
As a default an
ArgumentError
is raised if the user tries to set the field to an invalid enumeration value, to change this behaviour use the:whiny
option:class User < ActiveRecord::Base as_enum :gender, [:male, :female], :whiny => false end
-
Need translated keys et al in your forms? SimpleEnum provides a
<enum>_for_select
method:# on the gender field <%= select("user", "gender", User.genders_for_select) %> # or on the '_cd' field <%= select("user", "gender_cd", User.genders_for_select(:value))
Translations need to be stored like:
de: activerecord: enums: user: # the Model, as User.class.name.underscore genders: # pluralized version of :gender male: männlich female: weiblich
-
To define any option globally, like setting
:whiny
tofalse
, or globally enable:prefix
; all default options are stored inSimpleEnum.default_options
, this hash can be easily changed in your initializers or wherever:# e.g. setting :prefix => true (globally) SimpleEnum.default_options[:prefix] = true
Do not use the same name for the enum as for the column, note that this mode of use is deprecated starting with version 1.4.1, e.g.
# BAD as_enum :status, [:active, :inactive, :archived], :column => "status" # GOOD as_enum :project_status, [:active, :inactive, :archived], :column => "status"
Do not use states named after existing, or well known method names, like ‘new` or `create`, e.g.
# BAD, conflicts with Rails ActiveRecord Methods (!) as_enum :handle, [:new, :create, :update] # BETTER, prefixes all methods as_enum :handle, [:new, :create, :update], :prefix => true
Searching for certain values by using the finder methods:
User.where(:gender_cd => User.female)
Working with database backed values, now assuming that there exists a genders
table:
class Person < ActiveRecord::Base as_enum :gender, Gender.all.map { |g| [g.name.to_sym, g.id] } # map to array of symbols end
Working with object backed values, the only requirement to enable this is that a) either a field name name
exists or b) a custom method to convert an object to a symbolized form named to_enum_sym
(for general uses overriding to_enum
is perfectly fine) exists:
class Status < ActiveRecord::Base # this has a column named :name STATUSES = self.order(:name) end class BankTransaction < ActiveRecord::Base as_enum :status, Status::STATUSES end # what happens now? the id's of Status now serve as enumeration key and the # Status object as the value so... t = BankTransaction.new t.pending! t.status # => #<Status id: 1, name: "pending"> # and it's also possible to access the objects/values using: BankTransaction.statuses(:pending) # => 1, access by symbol (not) the object! BankTransaction.statuses.pending # => 1 BankTransaction.statuses.pending(true) # => #<Status id: 1, name: "pending">
-
Maybe the
:whiny
option should default tofalse
, so that generally no exceptions are thrown if a user fakes a request? -
Clean-up code and tests -> bring down LOC ;) (but maintain Code LOC vs. Test LOC ratio which is currently 1:2.9)
-
Make ‘:slim => true` the default option…?
-
@dmitry - bugfixes and other improvements
-
@tarsolya - implemented all the ruby 1.9 and rails 3 goodness!
-
@dbalatero - rails 2.3.5 bugfix & validator fixes
-
@johnthethird - feature for
_for_select
to return the values -
@sinsiliux - ruby 1.9 fixes and removed AR dependency
Copyright © 2011 by Lukas Westermann, Licenced under MIT Licence (see LICENCE file)