Ruby Style Question: storing hash constants with various possible values

It is rather a matter of style, I wonder what other people are doing.

Say I have a field in my database called a “status” for a blog post. And I want it to have several possible meanings, such as "draft", "waiting for review" and "sent", as an example.

Obviously, we don’t want to “hard code” these magic values ​​every time, it would not be DRY.

So sometimes I do something like this:

class Post STATUS = { :draft => "draft", :awaiting_review => "awaiting review", :posted => "posted" } ... end 

Then I can write code that references it later as STATUS[:draft] or Post::STATUS[:draft] , etc.

This works fine, but there are a few things that I don't like.

  • If you have a typo and something like STATUS[:something_that_does_not_exist] is called, it won’t cause an error, it just returns zero and can finish setting it up in the database, etc., before you notice the error
  • It doesn’t look clean or ruby ​​to write things like if some_var == Post::STATUS[:draft] ...

I don’t know, something tells me that there is a better way, but I just wanted to see what other people are doing. Thank you

+6
ruby ruby-on-rails
source share
6 answers

This is a common problem. Consider something like this:

 class Post < ActiveRecord::Base validates_inclusion_of :status, :in => [:draft, :awaiting_review, :posted] def status read_attribute(:status).to_sym end def status= (value) write_attribute(:status, value.to_s) end end 

You can use a third-party ActiveRecord plugin called symbolize to make this even easier:

 class Post < ActiveRecord::Base symbolize :status end 
+6
source share

You can use Hash.new and give it a block argument, which is called if the key is unknown.

 class Post STATUS = Hash.new{ |hash, key| raise( "Key #{ key } is unknown" )}.update( :draft => "draft", :awaiting_review => "awaiting review", :posted => "posted" ) end 

It is a little dirty, but it works.

 irb(main):007:0> Post::STATUS[ :draft ] => "draft" irb(main):008:0> Post::STATUS[ :bogus ] RuntimeError: Key bogus is unknown from (irb):2 from (irb):8:in `call' from (irb):8:in `default' from (irb):8:in `[]' from (irb):8 
+8
source share

You can use the class method to throw an exception on a missing key:

 class Post def self.status(key) statuses = { :draft => "draft", :awaiting_review => "awaiting review", :posted => "posted" } raise StatusError unless statuses.has_key?(key) statuses[key] end end class StatusError < StandardError; end 

Potentially, you can also use this method to store statuses as integers in a database, by changing your rows to integers (in a hash), converting column types and adding a getter and setter.

+2
source share

I do it like this:

 class Post DRAFT = "draft" AWAITING_REPLY = "awaiting reply" POSTED = "posted" STATUSES = [DRAFT, AWAITING_REPLY, POSTED] validates_inclusion_of :status, :in => STATUSES ... end 

Thus, you get errors if you make a mistake. If I have several sets of constants, I can do something like DRAFT_STATUS to distinguish between.

+1
source share

Take a look at attribute_mapper gem.

There is a related article that shows how you can handle the problem declaratively, for example (borrowed from the article):

 class Post < ActiveRecord::Base include AttributeMapper map_attribute :status, :to => { :draft => 1, :reviewed => 2, :published => 3 } end 

... which looks pretty stylish.

+1
source share

Even if this is an old post, for someone stumbling about it, you can use the fetch method on Hash, which causes an error (when the default value is not passed) if the given key is not found.

 STATUS = { :draft => "draft", :awaiting_review => "awaiting review", :posted => "posted" } STATUS.fetch(:draft) #=> "draft" STATUS.fetch(:invalid_key) #=> KeyError: key not found: invalid_key 
0
source share

All Articles