LoginSignup
1
2

More than 5 years have passed since last update.

RubyにSwiftっぽいenumを作りました

Last updated at Posted at 2017-03-18

Swiftのenumが使いやすくて好きです。
Rubyには標準でenumがなく、gemでも同じような動きをしてくれるものがなかったので、自作してみました。

swifty_enum | RubyGems.org
zeero/ruby-swifty_enum | GitHub

インストール

RubyGemsに登録済みなので、bundler経由で。

Gemfile
gem 'swifty_enum'
$ bundle

または直接。

$ gem install swifty_enum

使い方

enumを定義する

  1. クラスを作成し、SwiftyEnumモジュールをincludeします。(以下、enumクラスと呼びます)
  2. def_case 名前 [値] でenumの要素(Swiftでいうcase)を作成します。(以下、enumケースと呼びます)
  3. 指定した名前のサブクラスが定義されます。

サンプルはこんな感じです。

flag.rb
require 'swifty_enum'

class Flag
  include SwiftyEnum

  def_case :On, '1'
  def_case :Off, '0'
end

Flag::On                       # => Flag::On

enumケースに定義されるメソッド

Swiftでも用意されているhashvalue、rawvalueをenumケースで使えるようにしています。

Flag::On.hashvalue             # => 0
Flag::On.rawvalue              # => '1'

def_caseでrawvalueが指定されなかった場合は、hashvalueをrawvalueの代わりに使うようにしています。ここはSwiftと振る舞いが違うことになってしまうので少し悩んでいます。。。

enumケースの生成

rawvalueに指定した値を使って、enumクラスのgetメソッドからenumケースを生成できます。
定義していないrawvalueで呼び出すとnilになります。

Flag.get('1')                  # => Flag::On
Flag.get('0')                  # => Flag::Off
Flag.get('2')                  # => nil

enumケースはスイッチ文で分岐することもできます。

on = Flag.get('1')
case on
when Flag::Off then
  puts 'NG'
when Flag::On then
  puts 'OK'
else
  puts 'NG'
end
# >> OK

enumケースに独自メソッドを追加する

enumクラス定義内でdef_methodを使うことにより、enumケースに独自のメソッドを追加できます。
メソッドの戻り値はメソッドの最後の値になります。returnは使用できません。(LocalJumpErrorになってしまいます)

class Flag
  include SwiftyEnum

  def_case :On, '1'
  def_case :Off, '0'

  # メソッドを追加
  def_method :status do |enum_case|
    case enum_case
    when On then
      'OK'
    when Off then
      'NG'
    end
  end

  # 引数をもつメソッドは可変長引数として受け取ることができる
  def_method :status_with do |enum_case, args|
    if args.empty?
      enum_case.status
    else
      with = args.join(', ')
      "#{enum_case.status} with #{with}"
    end
  end
end

Flag::On.status                # => 'OK'
Flag::Off.status               # => 'NG'
Flag::On.status_with('foo')    # => 'OK with foo'

ブロックの引数に自分自身が渡ってくるのでケースで分岐させることができます。
定義内なのでenumクラスの名前まで(上記例ではFlag::)は省略することができます。

また注意点ですが、def_methodはすべてのenumケースを定義したあとに呼び出してください。

すべてのenumケースを取得する

Swiftのenumにはない機能ですが、すべてのenumケースを取得するenum_caseメソッドを追加しています。

Flag.enum_cases                # => [Flag::On, Flag::Off]

おわり

Swiftのenumの一通りの機能はつけれたかなと思ってます。
要望やご意見などありましたらコメントやGithubイシューをもらえると嬉しいです。

1
2
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
1
2