ActiveRecord::Enum の使い方と重複エラーの避け方

photo credit: Andrew* via photopin cc

このブログをご覧のみなさん、こんにちは。
掲題の通り、ActiveRecord::Enum の使い方と重複エラーの避け方をご紹介します。

Environment

  • OS: macOS Sierra
  • Ruby: ruby 2.4.1p111 (2017-03-22 revision 58053) [x86_64-darwin16]
  • Gems
    • rails (5.1.2)
    • enum_help (0.0.17)

ActiveRecord::Enum の使い方

ActiveRecord::Enum を使うと、プログラムからは名前でアクセスでき、DBには整数値で保存できます。

class Student < ActiveRecord::Base
  enum favorite_school_subject: { math: 1, science: 2 }
end
$ rails console

> student = Student.create(favorite_school_subject: 1)
=> #<Student ..., favorite_school_subject: "math", ...> 

> student.favorite_school_subject
=> "math"

> student.favorite_school_subject_before_type_cast
=> 1 # レコードに設定されている値

> student.favorite_school_subject = 2

> student.favorite_school_subject
=> "science"

ActiveRecord::Enum の重複エラー

好きな教科があるので、嫌いな教科を追加しましょう。

class Student < ActiveRecord::Base
  enum favorite_school_subject: { math: 1, science: 2 }
  enum disliked_school_subject: { math: 1, science: 2 }
end

しかし、このコードは、同じ名前(math, science)を2回使っているため、以下のエラーが発生します。

ArgumentError:
  You tried to define an enum named "disliked_school_subject" on the model "Student", but this will generate a instance method "math?", which is already defined by another enum.

従って、以下のように名前が被らないようにしなければなりません。

class Student < ActiveRecord::Base
  enum favorite_school_subject: { favorite_math: 1, favorite_science: 2 }
  enum disliked_school_subject: { disliked_math: 1, disliked_science: 2 }
end

しかし、この方法では、以下のように理解し易いコードではなくなってしまいます。

$ rails console

> student = Student.create(favorite_school_subject: 1)
=> #<Student ..., favorite_school_subject: "favorite_math", ...> 

> student.favorite_school_subject
=> "favorite_math"

> student.favorite_school_subject_before_type_cast
=> 1 # レコードに設定されている値

> student.favorite_school_subject = 2

> student.favorite_school_subject
=> "favorite_science"

ActiveRecord::Enum の重複エラーの避け方

enum には _prefix, _suffix というオプションがあり、接頭辞、接尾辞の指定が可能です。

このオプションを使うと、以下のように理解し易く、スッキリしたコードが書けます。

class Student < ActiveRecord::Base
  enum favorite_school_subject: { math: 1, science: 2 }, _prefix: true
  enum disliked_school_subject: { math: 1, science: 2 }, _prefix: true
end
$ rails console

> student = Student.create(favorite_school_subject: 1)
=> #<Student ..., favorite_school_subject: "math", ...> 

> student.favorite_school_subject
=> "math"

> student.favorite_school_subject_before_type_cast
=> 1 # レコードに設定されている値

> student.favorite_school_subject = 2

> student.favorite_school_subject
=> "science"