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画面は何もしなくても、以下のようにちゃんと文字列で表示される。
ちなみに数値でほしいときは、`[]`で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>
これで、数値を気にせずに、文字列を選択できるようになった!
ちょっとはまった点
最初はフォームの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>
画面の見た目は先ほどと変わらない。
ただし、この状態で更新を保存すると以下のエラーとなる。
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'
taskインスタンスのstatusフィールドに整数の0ではなく、文字列の”0”を渡そうとしてエラーになってしまう。
enum型は文字列で直接代入できるので、最初に示したようにkeysの値をoptionのvalueにも表示にも使うことで、このエラーを回避できる。
まとめ
Rails 4.1以降で固定値のフィールドを使うときは積極的にenum型を使っていくと便利!