読者です 読者をやめる 読者になる 読者になる

Rails Webook

自社のECを開発している会社で働いています。Rails情報やサービスを成長させる方法を書いていきます

いまさらながらRails4.1から導入されたEnumが便利なのでまとめてみた

Rails初級 Rails Model

f:id:nipe880324:20150710020807j:plain:w360
las - initially (Lori Semprevio) | Flickr - Photo Sharing!

Rails4.1から導入されたEnumの挙動について忘れることがあるので簡単にまとめました。
enumの定義、enumを定義したことにより使える便利なメソッドなどをまとめました。

動作確認

  • Rails 4.2.3
  • Ruby 2.2.2

enumの定義

次のように、enum、属性名、属性の値(ハッシュ)で指定することでEnumを定義することができます。

class Article < ActiveRecord::Base
  # enumの定義(キーと数字のハッシュを渡す。数字がDBカラムに設定される)
  enum status: { draft: 0, published: 1 }
end


DBカラムのデフォルト値をenumの初期値と合わせておきます。

class CreateArticles < ActiveRecord::Migration
  def change
    create_table :articles do |t|
      t.integer :status, default: 0, null: false, limit: 1

      t.timestamps null: false
    end

    add_index :articles, :status
  end
end


enumで使えるメソッド

enumで定義したハッシュのキー名+?でそのキーの値が設定されているかどうかを返します。
また、キー名+!で値を設定し、DBに保存します。

article = Article.new
#=> #<Article id: nil, status: 0, created_at: nil, updated_at: nil>

article.status #=> "draft"

# statusがdraftか確認
article.draft? #=> true

# statusがpublishedか確認
article.published? #=> false

# statusをpublishedに設定
article.published!
#=> INSERT INTO "articles" ("status", "created_at", "updated_at") VALUES (?, ?, ?)  [["status", 1], ["created_at", "2015-07-09 ..."], ["updated_at", "2015-07-09 ..."]]

article.status #=> "published"


enumで指定した属性名の複数形のメソッドを呼び出すと、enumで指定したハッシュを取得できます。

# enumの属性名の複数形でハッシュを取得できる
Article.statuses #=> {"draft"=>0, "published"=>1}

# ハッシュとしてアクセスできる
Article.statuses[:draft] #=> 0


enumで定義したハッシュの値以外を設定しようとするとexceptionが発生します。

# キー値で設定できる
article = Article.new(status: :published)
article.published? #=> true

# バリューでも設定できる
article = Article.new(status: Article.statuses[:published])
article.published? #=> true

# ハッシュで定義されていない値を設定しようとするとエラーが発生します
Article.new(status: 90) #=> ArgumentError: '90' is not a valid status
Article.new(status: :hoge) #=> ArgumentError: 'hoge' is not a valid status

enumのキーをスコープとして使うことができます。

Article.published.where('created_at > ?',  3.days.ago)
#=> SELECT "articles".* FROM "articles" WHERE "articles"."status" = ? AND (created_at > '2015-07-06 ...')  [["status", 1]]

# また、複数のキーを指定することもできますが、ANDなので検索結果は必ず0になってしまいます
Article.draft.published
#=> SELECT "articles".* FROM "articles" WHERE "articles"."status" = ? AND "articles"."status" = ?  [["status", 0], ["status", 1]]
#=> #<ActiveRecord::Relation []>


検索するときは次のようにもできます。

Article.where("status <> ?", Article.statuses[:published])
#=> SELECT "articles".* FROM "articles" WHERE (status <> 1)


Enumの注意点

enumを次のように配列でも定義できます。

class Article < ActiveRecord::Base
  enum status: [:draft, :published]
end

その場合、DBの値は、配列の添え字と同じ値になります。

article = Article.create
article.status #=> draft
# DB値は 0

そのため、値の追加、削除をしたときに、配列の追加場所を気にしないと、DBの値とenumのカラムの意味が異なり、バグが発生してしまいます。

class Article < ActiveRecord::Base
  # judgedを追加。judgedの値は配列の添え字の1になるので、
  # publishedだった記事がjudgedとして判断されてしまう
  enum status: [:draft, :judged, :published]
end

article = Article.create(status: Article.statuses[:published])
article.status     #=> "published" (statusが1として登録される)
article.published? #=> true

# enumの定義を更新する(judgedが1になる)

# 先ほど登録したArticleのstatusを確認するとjudged(1)になる
article = Article.last
article.status  #=> "judged"
article.judged? #=> true