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

# <i class="fa fa-heart fa-fw"></i>概要

Rails を使っていてちょくちょくお世話になる [単一テーブル継承 (Single Table Inheritance)](http://dev.classmethod.jp/server-side/ruby-on-rails/rails-sti-with-one-controller/) (以下 STI) の機能。
この機能を使うと (デフォルトでは) `type` という名前の列に、その ActiveRecord オブジェクトのクラス名がキャメルケースで設定されます。
そして、Rails 4.1 にて満を持して登場した [ActiveRecord::Enum](http://edgeapi.rubyonrails.org/classes/ActiveRecord/Enum.html)。
この STI 用の type 列を enum に流用しようというのが今回の趣旨です。

# <i class="fa fa-star fa-fw"></i>具体例

例として User モデル (スーパークラス) とそれを継承した Admin, Teacher, Student モデル (サブクラス) を取り上げます。
サブクラスのモデルは Rails の STI を利用して同じ users テーブルにレコードを保存します。

``` ruby:
class User < ActiveRecord::Base
end

class Admin < User
end

class Teacher < User
end

class Student < User
end
```

この場合、enum は User モデルに以下の様に定義します。

``` ruby:user.rb
class User < ActiveRecord::Base
  enum type: {
    admin:   'Admin',
    teacher: 'Teacher',
    student: 'Student'
  }
end
```

これで ActiveRecord::Enum が提供する機能を利用できます。

``` ruby
user = Student.create(name: '乃莉')
#=> #<Student id: 1, name: "乃莉" ...>

user[:type]
#=> "Student" # 実際の値を取得する。

user.type
#=> "student" # enum の 値を取得する。

user.teacher?
#=> false

user.student?
#=> true
```

これで type 列を enum に流用できました！

# <i class="fa fa-globe fa-fw"></i>I18n 対応

[enum_help](https://github.com/zmbacker/enum_help) という Gem を利用すると enum の I18n 対応 (日本語対応) が可能です。

``` yml:ja.yml
ja:
  enums:
    user:
      type:
        admin: 管理者
        teacher: 教員
        student: 学生
```

``` ruby
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 対応 (日本語対応) が適用されないのです。

``` ruby
Student.types_i18n
#=> {"admin"=>"admin", "teacher"=>"teacher", "student"=>"student"} # I18n 対応が適用されていない！

user = User.find(2)
#=> #<Student id: 2, name: "なずな," ...>

user.type_i18n
#=> "student" # I18n 対応が適用されていない！
```

## <i class="fa fa-lightbulb-o fa-fw"></i>解決策 その1

これは各サブクラスでもスーパークラスと同様に enum の定義をし、
さらに ja.yml も各サブクラスに対応する記述を行えば解決できます。

``` yml:ja.yml
ja:
  enums:
    user:
      type: &user_type
        admin: 管理者
        teacher: 教員
        student: 学生
    student:
      type:
        <<: *user_type
```

``` ruby:student.rb
class Student
  enum type: User.types
end
```

``` ruby
Student.types_i18n
#=> {"admin"=>"管理者", "teacher"=>"教員", "student"=>"学生"}

user = User.find(2)
#=> #<Student id: 2, name: "なずな," ...>

user.type_i18n
#=> "学生" # 日本語 ｷﾀ━━━━(ﾟ∀ﾟ)━━━━!!
```

しかし、この方法では言語ファイルの記述やコードの重複が増えてしまうので、別の方法を考えてみました。

## <i class="fa fa-lightbulb-o fa-fw"></i>解決策 その2

サブクラスの `SubClass.types_i18n` や `SubClass#type_i18n` をオーバーライドします。
なおメソッドのオーバーライドは、サブクラスがスーパークラスを継承した際に自動的に行われるように [Class#inherited](http://docs.ruby-lang.org/ja/2.2.0/method/Class/i/inherited.html) を利用して実現します。

``` ruby: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
```

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