ハートレイルズの技術スタッフによるブログです。

ハートレイルズの技術スタッフによるブログです。
ウェブサービス、スマートフォンアプリの制作に関連する技術的な情報を発信していきます。

2011年2月4日金曜日

状態遷移をスマートに実装するためのRailsプラグイン aasm

こんにちは。ハートレイルズの前島(@netwillnet)です。今回は aasm という、状態遷移をスマートに管理するためのRailsプラグインの紹介をしようと思います。

インストール方法

gem install aasm
だけ。

どういうときに使えるの?

複数の状態を持つモデルを管理するときに威力を発揮します。例として、所有している本の状態を管理するモデルを書いてみましょう。状態には

  • 未読(not_read)
  • 読んでる途中(reading)
  • 既読(read)
  • 捨てた(dump)

の4つがあるとし、 status カラムで管理します。また、別の状態として「友達に貸している」を管理する lending カラムを定義しているとします。

状態遷移系のメソッドを aasm を使わず、素直に書くと

class Book < ActiveRecord::Base
  # status  : 読書の状態を管理するカラム(integer)
  # lending : 友達に貸しているか否かを管理するカラム(boolean)

  before_create :set_initial_state

  STATUS = {:not_read => 0, :reading => 1, :read => 2, :dump => 3}

  def reading
    # 未読以外の状態/友達に貸している状態からは遷移させない
    if !status.nil? && status != STATUS[:not_read] || lending
      return
    end
    self.status = STATUS[:reading]
  end

  def read
    # 未読/読んでる途中以外の状態/友達に貸している状態からは遷移させない
    return if !status.nil? && status != STATUS[:not_read] &&  status != STATUS[:reading] || lending
    self.status = STATUS[:read]
  end

  def dump
    # 友達に貸している状態からは遷移させない
    return if lending
    self.status = STATUS[:dump]
  end

  private

  def set_initial_state
    self.status = STATUS[:not_read] if status.nil?
  end
end

こんな感じでしょうか。これくらいなら別に aasm を導入しなくても何とかなりますが、状態遷移させるかどうか判定する条件式が面倒ですね。もし、状態の数や条件がもっと多かったらどうでしょう・・・うんざりしてきますね。

これを aasm を使って書くと下記のようになります。

class Book < ActiveRecord::Base
  # status  : 読書の状態を管理するカラム(string)
  # lending : 友達に貸しているか否かを管理するカラム(boolean)

  include AASM

  aasm_column :status
  aasm_initial_state :not_read

  aasm_state :not_read
  aasm_state :reading
  aasm_state :read
  aasm_state :dump

  aasm_event :reading do
    transitions :to => :read, :from => :not_read, :guard => :not_lending?
  end

  aasm_event :read do
    transitions :to => :read, :from => [:not_read, :reading], :guard => :not_lending?
  end

  aasm_event :dump do
    transitions :to => :dump, :from => [:not_read, :reading, :read], :guard => :not_lending?
  end

  def not_lending?
    !lending
  end
end

どうでしょうか。だいぶすっきりしたように思いませんか?

それぞれのメソッドの意味

コードを見れば大体わかると思いますが、簡単に説明します。

aasm_column
aasm で扱うカラムを指定します。
aasm_initial_state
初期状態を symbol で指定します。
aasm_state
状態の候補を設定します。
aasm_event
引数に指定した symbol が、状態を変化させるためのメソッドになります。保存はされません。保存も同時にするには"!"をメソッド名末尾に付ける必要があります。 ブロック中の transition メソッドで 遷移先の状態(:toオプションで指定)と遷移前の状態(:fromオプションで指定)を設定します。:from で設定した以外での状態の時に遷移しようとすると、AASM::InvalidTransition の例外が発生し、遷移しません。また、:guard で指定したメソッドを実行して false が返ってきた場合も状態遷移しません(この場合は例外は発生せず、falseが返ります)。

まとめ

というわけで、 aasm を使うとこのような感じで複雑な状態管理をスマートに書くことが出来ます。みなさんも使ってみてはどうでしょうか。


この記事を気に入ってくださった方は、ぜひブックマーク、リツイートいただけると幸いです。

この記事をはてなブックマークに追加

ハートレイルズのファンページはこちら。今後ともどうぞよろしくお願いいたします。

ハートレイルズでは現在、新規のウェブサービス、ソーシャルアプリ (ゲーム)、スマートフォンアプリの受託開発案件を絶賛募集中です。どうぞお気軽にご相談ください! (スタートアップの方は投資/育成ページをご覧ください。)

0 件のコメント:

コメントを投稿