Rails脱初心者のための基礎知識と実装Tipsまとめ(2) - 区分値管理編

## はじめに

[第1回の設計編](http://qiita.com/future-hoshi/items/cba6a4a865e287bca940)に続き、第2回は区分値管理編をお送りします。

本記事はRails利用者だけでなく、他の言語・技術要素においても参考になるように記載したつもりです。

## 区分値管理とは

エンタープライズなシステムを作る場合には、必ずといっていいほど、登場するのが区分値です。一番簡単な例でいうと、会員マスタというテーブル上で、カラムに性別を保持すると場合、男性であれば1、女性であれば2というように、値に特別な意味を持たせてデータを保持する、伝統的なシステムの設計技法です。

国産のJava永続化フレームワークであるDBFluteのドキュメントにも下記のように解説されてますので、そもそも区分値ってなんだという方は参考にしてください。

* 区分値 (Classification) | DBFlute<br />
http://dbflute.seasar.org/ja/manual/function/genbafit/implfit/classification/#about 

## 日本と海外における区分値管理の違い

:us:海外では、プログラミング言語の列挙体を使って表すパターンが多いと思います。
例えば、先の性別の例だと、Javaだと下記のような実装になります。

```Java
/*******************
 * 性別
 ******************/
public enum Gender {
    MALE(1);   //男性
    FEMALE(2); //女性
}
```
※ C#などもほぼ同様の実装です。
<hr>

:jp:日本においては、上記の例でいうところの性別を「区分種別」（カテゴリ）、男性・女性を「区分名称、実際の値である1,2を狭義の区分値と呼んだりします。

このあたりは会社によってルールが違うので、そもそも区分を「コード」と呼ぶところもありますが、実現したいことは同じで、DBにマスタとしてテーブルを用意するほどでもない意味のあるデータの集合を総称して、広義の区分値と呼んでいるいるように思います。

例えば、ECサイトなどの会員登録画面をイメージした場合、ラジオボタンで男性と女性を選択するという場合などには、男性・女性というラベルをハードコーディングせずに、特定の区分種別の値を列挙させて区分名を取得するいう形で利用されており、上記のような列挙体では、この区分名を表せないことが問題になります。

#### Railsにおける列挙体
Rails4.1より、[ActiveRecord Enum](http://edgeapi.rubyonrails.org/classes/ActiveRecord/Enum.html)という列挙体の仕組みが利用できるようになりましたが、いわゆる区分値として利用するには、やはり機能が不足している感じる方も多いのではないでしょうか。

```rb:Ruby(Rails4.1以上)
class Member < ActiveRecord::Base
  enum gender: [:male, :female]
end
```

そこで、Railsにおける区分値の仕組みを検討してみます。

## 1. DBで管理する方法
そもそもDB管理するほどでもない・・・という前提をなかったことにして、専用テーブルを作ってしまう方法です。要するにマスタとして扱うということですね。

この方法には、さらに２つのバリエーションがあります。

### ①システム共通で区分値マスタを定義

#### 区分値マスタ（M_CLASS）
|ID   |区分種別名|区分物理名|区分論理名|区分値|
|:----|:-------|:-------|:--------|:----|
|1    |GENDER  |MALE    |男性      |1    |
|2    |GENDER  |FEMALE  |女性      |2    |

### ②区分種別ごとにマスタを作成
#### 性別マスタ（M_GENDER）
|ID   |区分物理名|区分論理名|区分値|
|:----|:-------|:--------|:----|
|1    |MALE    |男性      |1    |
|2    |FEMALE  |女性      |2    |

#### :o:メリット
* SQLで使用したい場合、joinを使ってSQLの世界の中で完結できる

#### :x:デメリット
* 多言語対応する場合、カラムを増やすか、名称マスタを子テーブルとして用意する必要がある。
* 毎回DBアクセスする必要がある（キャッシュすれば解決可能だが）
* 特定の区分値を実装の中で利用したい場合、定数クラスなどの実装をしないといけないため、DRYじゃない

Railsの場合は、1のようにシステム共通で用意してしまうと、いちいちwhereで区分種別を絞って・・・みたいな実装になるので、区分種別ごとにマスタを定義するほうがベターかと思います。

#### 参考
* tail -f pinzo.log: 自分流 Rails での区分値<br />
http://blog.mkt-sys.jp/2014/02/classifications-in-rails.html

## 2. 列挙体を拡張する方法
ActiveRecord Enumでは区分名の多言語対応ができないという課題を、[enum_help](https://github.com/zmbacker/enum_help)という:gem:gemで補完したり、i18n対応については自作するというアプローチです。

下記でも紹介されてました。

* Rails4 - ActiveRecord::Enum の使いドコロ - Qiita<br />
http://qiita.com/kakipo/items/4b6d0c70ca4f41fb6090
* Railsでenum+i18n - khondalitのWebを学ぶブログ<br />
http://khondalit.hatenablog.com/entry/2015/01/04/022236

ActiveRecord Enumの実装に加えて、下記のように言語別にyamlファイルを用意しておけば、i18n問題に対応が可能です。

```yaml:config/locales/model/member.ja.yml
ja:
  enums:
    member:
      gender:
        male: 男性
        female: 女性
```
#### :o:メリット
* Railsの標準の仕組み＋αなので、理解しやすく、実装時も便利

#### :x:デメリット
* ActiveRecordの特定のモデルに依存するため、複数テーブルで利用する汎用的な区分は管理しづらい
* 区分を列挙する仕組みなどは自分でいくらか拡張する必要がある

## 3. 独自の仕組みを利用する方法
Inumという日本人の方が作った独自の列挙体の仕組みがあります。

* RailsのEnumと相性が悪い人向けのInumをバージョンアップさせた話。 - 波打際のブログさん
http://alfa.hatenablog.jp/entry/2014/02/26/013835

* alfa-jpn/inum
https://github.com/alfa-jpn/inum

```rb:app/model/inum/gender.rb
class Gender < Inum::Base
  define :MALE,   1
  define :FEMALE, 2
end
```

```yaml:config/locales/inum.yml
ja:
  gender:
    male: 男性
    female: 女性
```

#### :o:メリット
* エンタープライズで要求される区分値管理の要件はほぼ満たせるだけの機能がある
* 区分種別の列挙や特定の区分のラベル取得など、実装がスムース

#### :x:デメリット
* Rails標準の仕組みではないので、いつまでサポートされるかは不明

## 結論
厳密な区分値管理をしたい方は、3のInumがオススメです。
ちなみに、私自身はこれと同じような仕組みを数年前自作して、それを仕事で使ってます。

小規模アプリや社内利用アプリなら、2の列挙体＋αで事足りると思います。

なお、1のDB管理は、rakeなどでバッチが多く、生SQLに近い実装が多いような場合には有効だと思います。

## Rails脱初心者のための基礎知識と実装Tipsまとめシリーズ目次

1. 設計編
http://qiita.com/future-hoshi/items/cba6a4a865e287bca940

2. 区分値管理編 :koko: