Proper storage of "enumeration" models in Rails
Apologies if the question is a little imprecise, but I'll describe my problem below.
I'm setting up some models in a Rails project, and one thing I've noticed I'm 开发者_Python百科running into more than a few times is dealing with attributes that meet the following criteria:
- They can be set to one of a small, predefined set of values
- Those values need to have both a name and an identifier (whether a numeric id, code, whatever)
- The values would only ever change as the result of a good deal of code change.
For example, one of my models should have a status
field that can be set to one of: Defining, Executed, or Completed. I need to show those specific words within the interface, but I don't want to store the strings in the DB in case I need to change them in the future (or internationalize, or whatever.)
The obvious option is to define models for each of these models, but that seems to present a good deal of overhead in maintaining the models, ensuring that I write migrations between environments, etc. for each one of these, which seems like a lot of overhead.
The other option is to store it as an integer, and whip up an "enumeration" type class that stores the translation of those values - this would probably work fine, but I'm concerned that I'll lose associations and other handy stuff I get from ActiveRecord models.
Any advice on the best way to handle this situation?
Check out the ruby gem I've been working on called classy_enum. I'm pretty sure it does exactly what you're looking for. The README has some example usage, but the premise is that it lets you define multiple enum members as classes that can have different properties.
Define a varchar
or ENUM
in the database and validate the field in the model:
validates_inclusion_of :status, :in => %w(Defining Executed Completed)
Rails will treat it like a string field, but it still validates what the values are.
If you really need to abstract the text of the status field, you could just save it as an integer:
class Foo < ActiveRecord::Base
STATUS_DESCRIPTIONS = %w(Defining Executed Completed)
def status
STATUS_DESCRIPTIONS[ read_attribute(:status) ]
end
end
If it gets any more complicated than that, you should try @Beerlington's gem.
I was looking for a similar solution when I ran into the enumerize gem. I like its clean and simple DSL.
If your states contain a lot of state specific knowledge, then the state machine gen suggested by scaney might be a good idea. The other option is to use the good old state pattern with the state_pattern gem.
sounds like you might want a state_machine, see here: https://github.com/pluginaweek/state_machine
How about putting it in a module and mixing it into models:
module StatusCodes
DEFINING = 1
EXECUTING = 2
COMPLETED = 3
def status
return "" unless self[:status] # handle nil
const_lookup = self[:status] - 1 # index to module constants
StatusCodes.constants[const_lookup].to_s.downcase.camelcase # note: needs Ruby 1.9
end
end
class MyModel < ActiveRecord::Base
include StatusCodes
end
Now add an integer status
column to the model and you can assign like so:
m = MyModel.new(:status=>StatusCodes::DEFINING)
and retrieve a string:
m.status # "Defining"
Just for completeness, Rails has a Enum class since 4.1.
along with the other excellent options, you could use this if you choose your last option outlined:
http://github.com/jasondew/coded_options
(used to work with Jason, and we used a predecessor to this in several rails apps, especially if the user ever selects the option from a select tag)
精彩评论