Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ruby Style Question: storing hash constant with different possible values

This is more of a style question, I'm wondering what other people do.

Let's say I have a field in my database called "status" for a blog post. And I want it to have several possible values, like "draft", "awaiting review", and "posted", just as an example.

Obviously we don't want to "hard code" in these magic values each time, that wouldn't be DRY.

So what I sometimes do is something like this:

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

  ...

end

Then I can write code referring to it later as STATUS[:draft] or Post::STATUS[:draft] etc.

This works ok, but there are a few things I don't like about it.

  1. If you have a typo and call something like STATUS[:something_that_does_not_exist] it won't throw an error, it just returns nil, and may end up setting this in the database, etc before you ever notice a bug
  2. It doesn't look clean or ruby-ish to write stuff like if some_var == Post::STATUS[:draft] ...

I dunno, something tells me there is a better way, but just wanted to see what other people do. Thanks!

like image 572
Brian Armstrong Avatar asked Jan 21 '10 04:01

Brian Armstrong


3 Answers

You can use Hash.new and give it a block argument which is called if a 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's a bit messy 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
like image 173
Farrel Avatar answered Nov 18 '22 12:11

Farrel


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
like image 31
John Feminella Avatar answered Nov 18 '22 11:11

John Feminella


You could use a class method to raise 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 could also use this method to store the statuses as integers in the database by changing your strings to integers (in the hash), converting your column types, and adding a getter and a setter.

like image 2
Alex Reisner Avatar answered Nov 18 '22 10:11

Alex Reisner