[Ruby]Enumの値を1つのキーに対し複数持たせる方と特定キーの値のみ全取得する方法


はじめに

この記事について

RubyでEnumのキーに対して複数の値を持たせる記述方法が分からず、個人的に苦戦しましたので備忘録として残しておきます。
また、複数持たせた値をRailsのValidatesの in に利用する(特定のキーの値のみ全取得する)方法も不明でしたがわかりましたので、併せて書きます。

筆者実行環境

  • ruby 2.6.3
  • rails 6.0.2

やりたかった事

例えば、給料を表すEnumがあったとして、以下の要件を満たす必要があります。

要件:キー1つに対し、複数のバリューを持たせる。

  • Key
    • 役職
  • Value
    • 毎月の給与
    • ボーナス

また、API開発などで、ラジオボタンやセレクトボックスに特定の業務用の種別を持たせ、選択させリクエストされた文字列や値が、サーバー側で想定している定義値と合っているかを確認することがあった場合に in で利用できないと困るので、試行錯誤してみました

で、どうやんの?

以下でいけました
※金額は適当です

定義方法

  enum salary: { shinjin: {base: 160000, bonus: 200000},
                 ippan: {base: 200000, bonus: 400000},
                 butyo: : {base: 400000, bonus: 800000} }

使う時

inspectで見たところ、定義時と同様で中身はハッシュのハッシュになっているようなので、以下の要領で取得しないといけないようです

salary[:shinjin][:base] # 160000が返却される

validetesの in に利用する方法

前述したように単一のものであればEnumはハッシュのハッシュであるため salary[:shinjin][:base] で取得ができましたので、純粋に salary.map { |s| s[:base] } としてあげれば [160000, 200000, 4000000] として取得ができると思い込んでいたのですが、mapで展開すると s["shinjin", {"base"=>160000, "bonus"=>200000"}] と内部的にはなっているようで、mapし終えた後は通常通りの取得方法では取れないようなので、以下のようにすれば取得できます。
型の異なる二次元配列になっているになっているため、 s の1番目はKeyで2番目は複数の値をもったハッシュである。ということですね。

  # s[1][:base]で [160000, 200000, 4000000] が取得でき、inに対応できる
  validates :salary, inclusion: { in: salary.map { |s| s[1][:base] } }, if: Proc.new { |f| f.salary.present? } 

(おまけ)他の言語での複数Enum値の書き方と使い方

筆者はJavaやSwiftをやっているので、この記事に辿りついた方がどちらかの言語習得者で私と同じ境遇であった場合に役立つように、今回のケースと同じことをJavaとSwiftで表現します(その逆もしかり)

Java

Javaは定義順(コンストラクタのArg順?)で列に対応するフィールドが決定されるので、以下の書き方ができます。

定義方法

public enum Salary {
    SHINJIN(160000, 200000),
    IPPAN(250000, 400000),
    BUTYO(400000, 800000),
    ;

    private final int base;
    private final int bonus;

    private Type(final int base, final int bonus) {
       this.base = base;
       this.bonus = bonus;
    }

    public String getBase() {
        return this.base;
    }

    public int getBonus() {
        return this.bonus;
    }
}

使う時

Salary.SHINJIN.base // 160000が返却される

Swift

Swiftはいろいろな書き方があるようですが、computed propertyで定義する

定義方法

enum Salary: Int {

  case shinjin
  case ippan
  case butyo

  var base: Int {
    switch self {
    case .shinjin:
        return 160_000
    case .ippan:
        return 250_000
    case .butyo:
        return 400_000
    }
  }

  var bonus: Int {
    switch self {
    case .shinjin:
        return 200_000
    case .ippan:
        return 400_000
    case .butyo:
        return 800_000
    }
  }

}

使う時

(javaと同じ)

Salary.shinjin.base // 160000が返却される

最後に

いかがでしたでしょうか。同じような境遇にある方の助けに少しでもなればと思います。
また、私はRubyとRails初心者なので、もっといい書き方や理解が足りていないことがあれば教えていただきたいです。

参考文献

rbenvでrubyのバージョンを管理する