LoginSignup
32
29

More than 5 years have passed since last update.

単一テーブル継承用の type 列を enum として利用する

Last updated at Posted at 2015-01-28

概要

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 モデルに以下の様に定義します。

user.rb
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.yml
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_i18nStudent#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.yml
ja:
  enums:
    user:
      type: &user_type
        admin: 管理者
        teacher: 教員
        student: 学生
    student:
      type:
        <<: *user_type
student.rb
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_i18nSubClass#type_i18n をオーバーライドします。
なおメソッドのオーバーライドは、サブクラスがスーパークラスを継承した際に自動的に行われるように Class#inherited を利用して実現します。

user.rb
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

これでサブクラスが増えても安心ですね!

32
29
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
32
29