カタカタブログ

SIerで働くITエンジニアがカタカタした記録を残す技術ブログ。Java, Oracle Database, Linuxが中心です。たまに数学やデータ分析なども。

Rails 4.2  ActiveRecord enumsを使って列挙型を扱ってみる

Rails 4.1からActiveRecordでenumが扱えるようになったので、今さらながら試してみた。
enum型あるいは列挙型は、あるカラムが取りうる固定値がいくつかに決まっている場合にDB上は数値型で保持することで効率よくデータを格納したり検索できるようにする一方で、アプリケーション側のソースコード上では可読性を損なわないようにしたいときに使われる。

Rails標準機能なので、4.1以降であればGemを追加することなく、そのまま`enum`が使えるのがうれしい。
今回は例として、タスク管理アプリケーションのサンプルで確認してみる。Taskモデルにstatusというフィールドがあり、0は「新規」、1は「作業中」、2は「完了」を表すようにしたい。

ちなみに当環境のRubyとRailsのバージョンは以下です。

OS X EI Capitan バージョン 10.11.2
Rails 4.2.3
ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-darwin14]

サンプルアプリ作成

TaskモデルのCRUD画面をscaffoldで作成してdb:migrateする。

$ rails g scaffold Task title:string body:string status:integer
$ rake db:migrate

Taskモデルのstatusフィールドをenum型で定義してみる。定義するときは配列またはハッシュで指定する。

app/models/task.rb

class Task < ActiveRecord::Base
  enum status: %w(新規 作業中 完了)
end

これだけで設定は完了。早速試してみる。

> Task.create(title: "部屋の掃除", body: "リビングに掃除機をかける", status: "新規")
=> #<Task id: 2, title: "部屋の掃除", body: "リビングに掃除機をかける", status: 0, created_at: "2015-12-19 02:43:58", updated_at: "2015-12-19 02:43:58">

createメソッドのstatusフィールドに”新規”という文字列を直接渡してやると、ちゃんとDB上のstatusカラムの値は0になっている。実際の数値は配列で作った場合はそのindex値が使われるので、数値を指定したい場合は以下のようにハッシュで直接指定する。

class Task < ActiveRecord::Base
  enum status: {新規:10, 作業中:20, 完了:30}
end

また、Taskインスタンスを直接`status`メソッドで呼び出すと文字列で取得できるようになっている。

>task.status #=> "新規"

そのため、scaffoldで作ったindex画面は何もしなくても、以下のようにちゃんと文字列で表示される。
f:id:osn_th:20151219144742p:plain

ちなみに数値でほしいときは、`[]`でenumのカラム名をシンボルで渡すと取得できる。

> task[:status] #=> 0

編集画面でenum型をリストボックスから選ぶ

scaffoldのformだとf.number_fieldになっているので、selectビューヘルパーを使ってenum型で定義した項目を選択できるように変更してみる。

enum型したカラムを複数形にしたクラスメソッドを呼ぶと、列挙型の文字列と数値のハッシュを取得できるので、これをoptionの項目に利用する。

> Task.statuses #=> {"新規"=>0, "作業中"=>1, "完了"=>2}

フォームを以下のように直す。
app/views/tasks/_form.html.erb

<div class="field">
    <%= f.label :status %><br>
    <%= f.select :status, Task.statuses.keys %>
  </div>

これで以下のようなselectボックスができる。

<select name="task[status]" id="task_status">
  <option selected="selected" value="新規">新規</option>
  <option value="作業中">作業中</option>
  <option value="完了">完了</option>
</select>

これで、数値を気にせずに、文字列を選択できるようになった!
f:id:osn_th:20151219144744p:plain

ちょっとはまった点

最初はフォームのselectビューヘルパーに以下のようにkeysを使わないでハッシュを直接渡してた。
app/views/tasks/_form.html.erb

<div class="field">
    <%= f.label :status %><br>
    <%= f.select :status, Task.statuses %>
  </div>

こうするとselectボックスのHTMLは以下のようになる。optionのvalueが数値になる。

<select name="task[status]" id="task_status">
  <option value="0">新規</option>
  <option value="1">作業中</option>
  <option value="2">完了</option>
</select>

画面の見た目は先ほどと変わらない。
f:id:osn_th:20151219144746p:plain
ただし、この状態で更新を保存すると以下のエラーとなる。

ArgumentError in TasksController#update
'0' is not a valid status

app/controllers/tasks_controller.rb:44:in `block in update'
app/controllers/tasks_controller.rb:43:in `update'

f:id:osn_th:20151219144750p:plain

taskインスタンスのstatusフィールドに整数の0ではなく、文字列の”0”を渡そうとしてエラーになってしまう。
enum型は文字列で直接代入できるので、最初に示したようにkeysの値をoptionのvalueにも表示にも使うことで、このエラーを回避できる。

まとめ

Rails 4.1以降で固定値のフィールドを使うときは積極的にenum型を使っていくと便利!