开发者

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 =>开发者_JAVA百科 "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!


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


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


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.


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

This way you get errors if you misspell one. If I have multiple sets of constants, I might do something like DRAFT_STATUS to distinguish.


Take a look at the attribute_mapper gem.

There's a related article that shows how you can handle the problem declaratively, like this (borrowed from the article):

class Post < ActiveRecord::Base
  include AttributeMapper

  map_attribute :status, :to => {
    :draft => 1,
    :reviewed => 2,
    :published => 3
  }
end

...which looks rather stylish.


Even though this is an old post, for somebody stumbling across this, you can use the fetch method on Hash, which raises an error (when no default is 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

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜