下書きに入ってたから掘り起こすシリーズ。
statesmanを使った状態遷移を伴うコードを読む機会があって、眺めてたら図に書き出したくなったのでコードを書いた。
書き出したいStatesmanの状態定義
サンプルとしてStatesmanのREADMEにあった状態定義をそのまま持ってくる。
# order_state_machine.rb class OrderStateMachine include Statesman::Machine state :pending, initial: true state :checking_out state :purchased state :shipped state :cancelled state :failed state :refunded transition from: :pending, to: [:checking_out, :cancelled] transition from: :checking_out, to: [:purchased, :cancelled] transition from: :purchased, to: [:shipped, :failed] transition from: :shipped, to: :refunded end
statesmanのメソッド郡が使えるようになるしくみ
statesman を使ってリソースの状態遷移を扱うためには使いたいクラスで Statesman::Machine
を include する必要がある。includeすると、そのクラスにクラスメソッドが定義される。
仕組みをシンプルに書くと以下のとおりとなる。
irb(main):001:1* module Foo irb(main):002:2* module Bar irb(main):003:3* def self.included(base) irb(main):004:3* base.extend(ClassMethod) irb(main):005:2* end irb(main):006:2* irb(main):007:3* module ClassMethod irb(main):008:4* def baz irb(main):009:4* puts "Hello!" irb(main):010:3* end irb(main):011:2* end irb(main):012:1* end irb(main):013:0> end => :baz irb(main):014:1* class A irb(main):015:1* include Foo::Bar irb(main):016:0> end => A irb(main):017:0> A.baz Hello! => nil
self.included
で module Bar
が include
されたときの処理を記述できる。引数はインクルードしたオブジェクトである。
docs.ruby-lang.org
base.extend(ClassMethod)
はつまるところ A.extend(ClassMethod)
となる。extend
は Object#extend
で、Object に対して特異メソッドとして引数に渡したモジュールのインスタンスメソッドを定義できる。ここでは ClassMethod#baz
が Aクラスの特異メソッドとして定義される。
docs.ruby-lang.org
クラスの特異メソッドということはクラスメソッドとおおよそ同義として扱って良い。 docs.ruby-lang.org
mermaid の状態遷移図を出力する
実装方法がわかったので、 Statesman::Machine
をいい感じに再実装することで状態の宣言と状態遷移のルールの記述に対して好きなことができる。あとはその再実装が適用される状態で直接statemachineのファイルをrubyで実行すればよい。ということで、mermaidのstateDiagramを書き出すコードを以下のgistに結果も含めてまとめた。
今回はRubyの実装の再定義だったので、中身を調べて上書きすればよかったのだけれども、なんらかのDSLに対してRubyのメソッドを実装して読み込ませてなんかするってのはbuildersconでやってたLTが自分の中で印象に残っている。今回のstatemachineのクラスを眺めてなんかできそうと思ったのもこのLTを思い出したことがきっかけだった。
www.slideshare.net