73
61

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Rails で使える StateMachine Gem 3 つをしらべてみた

Last updated at Posted at 2016-03-08

Rails 上で ActiveRecord を使って status を保存できる statemachine 系の gem として、だいたい aasm で作ってたので twitter でなんかないかなぁと書いたところ、以下の返答をもらった。

実はこの返答を得る前に aasm をいれちゃってたんだけど、今回はスパイク中のコードで使ったので、本実装時に再度選ぶことになるだろうから、aasm と教えてもらったものを簡単にまとめておく。

TL;DR;

どれを選ぶかは要件次第なので、これが絶対にお勧めということを書く気はないんだけど、私だったら以下のような選択するかなぁ。

  • ActiveRecord の enum を利用したいなら aasm
  • 状態の遷移をトラッキングしたい履歴が重要になるなら statesman
  • 状態が遷移した時間は必要になりそうなら transitions

aasm

GitHub での star は、この 3 つの中では最多の 2,236(2016/03/08 19:00 現在)。
3 つの中で唯一過去プロジェクトで使ったことがあるやつ。

state 管理したいクラスに AASM を include し、aasm do ... end というブロックに state 管理のためのコードをまとめる。
ActiveRecord との連携では、デフォルトでは aasm_state というカラムを利用することになるが、Rails の enum カラムを利用しても state 管理ができる。

対象の ActiveRecord クラスには、クラス.state_name のような scope と、オブジェクトには オブジェクト.state_name? で期待する state であるかというメソッドと オブジェクト.may_state_name? という、指定した state に遷移できるかを確認するメソッドができる。

transition は event :event_name do ... end というブロックに書いていき、object.event_name で遷移できるようになる。object.event_name! で保存までやってくれる。

event や transition 単位に、beforeaftererror のコールバックを記述しておくことができる。他にもコールバックとして差し込めるポイント は複数用意されている。

また、各 transition ごとに guard 条件を設定できるので、state の状態以外で遷移できるかの可否を記述することもできる。

以下、サンプルコード。

class Item < ActiveRecord::Base
  include AASM
  
  enum state: {wip: 0, ship: 10, archived: 100}
  
  aasm column: :state, enum: :true do
    state :wip, initial: true
    state :ship
    state :archive
    
    event :publish do
      before do
        self.published_at = Time.current
      end
      transitions from: :wip, to: :ship
    end
    
    event :archive do
      transitions from: :wip,  to :archive
      transitions from: :ship, to :archive
    end
  end
end

statesman

こちらの GitHub の star 数は、694(2016/03/08 19:00 現在)。

stateman は、他の 2 つと違い StateMachine 用のクラスが必要になるのが特徴。
statemachine を導入すると最初は少なかった状態数がプロジェクトが進むにつれ徐々に爆発していくことがあるので、その点ではスッキリ書けそう。

statetransitionguard_transition, before_transition などで、状態管理のための処理を宣言的に書ける。

状態を保存するには ActiveReocrd で、StateMachine を保存する用の table を用意したり、State 管理したい対象に関連をもたせてあげたりといろいろしてあげないといけないことが多い。以下の migrate 生成コマンドを実行する必要あり。

$ rails g statesman:active_record_transition 状態管理をしたいクラス名 状態保存用クラス名

しかし、各 state をカラムじゃなくレコードとして持つことができるので、状態遷移の履歴を後から参照できる。

状態を確認したいときや、遷移させたいときは、aasm のようにはいかず、current_statecan_transition_to?(state名)transition_to(state名) のように指定しないといけない。Scope も in_state(state名) のようになる。

transitions

こちらの GitHub の star 数は、464(2016/03/08 19:00 現在)。

この gem は aasm のように、state_machine do ... end で state 管理したいクラスに情報を記述していくスタイル。
state で 状態を記述し、event :event_name do ... end でイベントを書いて、transitions でトランジションを書くのも似てる。
コールバックのタイミングや種類は独特な感じで、state に対しての enterexit に対してもコールバックがセットできるのが他の 2 に比べると特徴的。

過去に aasm を使ったときは、だいたいほとんどの event に対して before で何かしらのタイムスタンプをいれるようにしていたんだけど(本当に必要だったかは要検討)、transitions にはタイムスタンプをいれるためのサポートある。

さいごに

また、これを書いている間に新たな statemachine が誕生しそうな息吹を感じた。

今回、3 つの gem を比較してみたけど、今後とりあえず導入するなら transitions を選択してみるかなあ。
とはいえ、だいたい適当に選ぶとつらいめにあうので、ある程度その state をどう使いたいかという用途の検討はしてから選びたい。

あと、他におもしろいのあったら教えてください !

73
61
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
73
61

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?