🦔

state machine の statesman を In Memory で使ってみる

2 min read

statesman を使ってみました

rails で state machine が欲しくなったので statesman を使ってみました。

なぜか ruby toolbox では state_machines のカテゴリに入ってませんでしたが、よく使われてメンテナンスも続いているようには見えます。
(ruby 久しぶりだったんですが、今どきは ruby toolbox もあまり使わないんでしょうか)

In Memory で動かす

statesman の特徴は state machine となる class と遷移の履歴を保存する transition class を model とは別に作るのが基本になっているところですかね。
state machine も勝手に model に生えるとかではなく、自分で定義して加えるので勝手にいろいろな機能が追加されていく訳ではなく驚きが少ない気がします。

Activity という model があるとして、state machine になるクラスを作ってinclude Statesman::Machine、model にstate_machineメソッドを作って、そこで state machine のインスタンスをセットすればまずは動きます。

class Activity < ApplicationRecord
  def state_machine
    @state_machine ||= ActivityStateMachine.new(self)
  end
end

class ActivityStateMachine
  include Statesman::Machine
  
  state :pending, initial: true
  state :done
  
  transition from: :pending, to: :done
end

こうやって state_machine を呼び出して使います

> activity = Activity.new
> activity.state_machine.current_state?
=> "pending"
> activity.state_machine.can_transition_to?(:done)
=> true
> activity.state_machine.transition_to!(:done)
=> true

# 状態遷移が出来ました
> activity.state_machine.current_state
=> "done"

一応、こういう感じに状態の管理は DB を使わずに In Memory で動作するのですが、基本的には状態遷移の記録を取る transition class を指定して動かす方が普段のユースケースには合うかもです。なにしろこのままでは Activity model の状態を保存出来ないので

> activity.save

# ここで保存した activity をロードしなおしたとすると
> activity2 = Activity.last
# 状態を確認すると initial state に戻ってしまってます
> activity2.state_machine.current_state
=> "pending"

(一応)RDB を使って状態を保存する

transition class を作って、DB に保存するようにすれば動きました。
そのやり方は README のままなので詳細はそっちを参考にしてもらうとして、initializers/statesman.rb でも作って ActiveRecord のアダプタを設定して、transition class を作成して migration をかけたら動くと思います

rails g statesman:active_record_transition Activity ActivityTransition

ただ、これは最初に動かした時の記憶だけなので参考までですが、
作成される migration ファイルが微妙で、id の関連付けで落ちるかもです。rake db:rollback も index の削除に引っかかったので add_foreign_key の位置を最後に変更したりしました

# これを migration ファイルの最後に置いておくと、まず foreign_key が消えるので、他の add_index も rollback 可能になる
add_foreign_key :activity_transitions, :activities

(なんかわかりにくい説明かも。すいません!)