概要
Rails を使っていてちょくちょくお世話になる 単一テーブル継承 (Single Table Inheritance) (以下 STI) の機能。
この機能を使うと (デフォルトでは) type
という名前の列に、その ActiveRecord オブジェクトのクラス名がキャメルケースで設定されます。
そして、Rails 4.1 にて満を持して登場した ActiveRecord::Enum。
この STI 用の type 列を enum に流用しようというのが今回の趣旨です。
具体例
例として User モデル (スーパークラス) とそれを継承した Admin, Teacher, Student モデル (サブクラス) を取り上げます。
サブクラスのモデルは Rails の STI を利用して同じ users テーブルにレコードを保存します。
class User < ActiveRecord::Base
end
class Admin < User
end
class Teacher < User
end
class Student < User
end
この場合、enum は User モデルに以下の様に定義します。
class User < ActiveRecord::Base
enum type: {
admin: 'Admin',
teacher: 'Teacher',
student: 'Student'
}
end
これで ActiveRecord::Enum が提供する機能を利用できます。
user = Student.create(name: '乃莉')
#=> #<Student id: 1, name: "乃莉" ...>
user[:type]
#=> "Student" # 実際の値を取得する。
user.type
#=> "student" # enum の 値を取得する。
user.teacher?
#=> false
user.student?
#=> true
これで type 列を enum に流用できました!
I18n 対応
enum_help という Gem を利用すると enum の I18n 対応 (日本語対応) が可能です。
ja:
enums:
user:
type:
admin: 管理者
teacher: 教員
student: 学生
User.types_i18n
#=> {"admin"=>"管理者", "teacher"=>"教員", "student"=>"学生"}
user = Student.create(name: 'なずな')
#=> #<Student id: 2, name: "なずな," ...>
user.type_i18n
#=> ...???
しかし、このままでは問題があります。STI を利用しているせいか、サブクラス (例えば Student) 経由で Student.types_i18n
や Student#type_i18n
を利用しても I18n 対応 (日本語対応) が適用されないのです。
Student.types_i18n
#=> {"admin"=>"admin", "teacher"=>"teacher", "student"=>"student"} # I18n 対応が適用されていない!
user = User.find(2)
#=> #<Student id: 2, name: "なずな," ...>
user.type_i18n
#=> "student" # I18n 対応が適用されていない!
解決策 その1
これは各サブクラスでもスーパークラスと同様に enum の定義をし、
さらに ja.yml も各サブクラスに対応する記述を行えば解決できます。
ja:
enums:
user:
type: &user_type
admin: 管理者
teacher: 教員
student: 学生
student:
type:
<<: *user_type
class Student
enum type: User.types
end
Student.types_i18n
#=> {"admin"=>"管理者", "teacher"=>"教員", "student"=>"学生"}
user = User.find(2)
#=> #<Student id: 2, name: "なずな," ...>
user.type_i18n
#=> "学生" # 日本語 キタ━━━━(゚∀゚)━━━━!!
しかし、この方法では言語ファイルの記述やコードの重複が増えてしまうので、別の方法を考えてみました。
解決策 その2
サブクラスの SubClass.types_i18n
や SubClass#type_i18n
をオーバーライドします。
なおメソッドのオーバーライドは、サブクラスがスーパークラスを継承した際に自動的に行われるように Class#inherited を利用して実現します。
class User < ActiveRecord::Base
enum type: {
admin: 'Admin',
teacher: 'Teacher',
student: 'Student'
}
# type 列を enum に流用した場合、enum_helper による i18n 対応が効かない模様。
# そのため User.types_i18n, User#type_i18n メソッドを override する。
def self.inherited(klass)
klass.class_eval do
def self.types_i18n
User.types_i18n
end
def type_i18n
User.types_i18n[type]
end
end
super
end
end
これでサブクラスが増えても安心ですね!