simanのブログ

ゆるふわプログラマー。競技プログラミングやってます。Ruby好き

Rubyで状態遷移を管理するgemを作った(state_transition)

https://github.com/jakesgordon/javascript-state-machine

このjavascript-state-machineが便利だったので、Rubyでも使いたいと思い、Ruby versionを作ってみました。
https://github.com/siman-man/state_transition

gemのインストール

gem install state_transition


サンプルコード

require 'state_transition'

state = StateTransition::StateMachine.new({
  initial: :first,

  actions: [
    { name: "move_first", from: [ :second, :third ], to: :first },
    { name: "move_second", from: [ :first, :third ], to: :second },
    { name: "move_third", from: :second, to: :third },
  ],

  callbacks: {
    before_second: -> { puts "before_second" },
    after_second: -> { puts "after_second" },
  }
})

puts state.current            #=> :first
puts state.can_move?(:second) #=> true
puts state.can_move?(:third)  #=> false

state.move_second           

puts state.current            #=> :second


実行結果

first
true
false
before_second
after_second
second


初期化について

初期化はStateTransitionモジュールのStateMachineクラスを使用します。
渡す引数は、initial, actions, callbacksの3つです(callbacksは任意)

state = StateTransition::StateMachine.new({
  initial: :first,

  actions: [
    { name: "move_first", from: [ :second, :third ], to: :first },
    { name: "move_second", from: [ :first, :third ], to: :second },
    { name: "move_third", from: :second, to: :third },
  ],

  callbacks: {
    before_second: -> { puts "before_second" },
    after_second: -> { puts "after_second" },
  }
})

3つの引数について説明します。

・initial (初期状態の宣言)

initial: :first

この部分で最初の初期状態を決定します。

・actions (移動時のアクション宣言)

  actions: [
    { name: "move_first", from: [ :second, :third ], to: :first },
    { name: "move_second", from: [ :first, :third ], to: :second },
    { name: "move_third", from: :second, to: :third },
  ]

配列の各要素に、それぞれアクションを定義しており、「アクション名、移動元、移動先」といった感じで定義していきます。移動元は複数指定可能ですが、移動先は1つだけです。また、移動元、移動先で定義されている状態は自動的に作成されます。


actionsで定義した状態遷移図

f:id:simanman:20130820141842j:plain


・callbacks

ここでは遷移前と遷移後に実行させたいプログラムを記述することが出来ます。移動前のメソッド名は「before_ + "状態名"」、移動後のメソッド名は「after_ + "状態名"」で固定です。

  callbacks: {
    before_second: -> { puts "before_second" },
    after_second: -> { puts "after_second" },
  }


アクション実行

actionsで定義したアクションを実行することで、状態を遷移させることが出来ます。移動の際にcallbacksが定義されている場合はそれを実行します。この場合「before_second」->「current = :second」->「after_second」と実行されます。

puts state.current            #=> :first

state.move_second           

puts state.current            #=> :second


その他

・current

currentはStateMachineクラスのインスタンス変数で、現在の状態が入っています。

state.current            #=> :first

・can_move?

can_move?メソッドでは現在の状態から引数で渡した状態に遷移できるかどうかをチェックします。

state.current            #=> :first
state.can_move?(:second) #=> true
state.can_move?(:third)  #=> false



いままでgemを作ったことがなかったのですが、bundlerを使用すると簡単に作成できました。本当はgem名を「state_machine」とかにしようと思ったのですが、既に使われていてダメでした。