aasmとは何か?acts_as_state_machineとの違いは?

restful_authenticationを--statefulオプションで利用すると、2つのstate_machineが用意されていることに気付く。一つは、以前から利用しているRailsプラグインacts_as_state_machine。で、もう一つがgemでインストールするaasm。aasmってacts_as_state_machineの頭文字だよな?と思いながら、デフォルトではacts_as_state_machineの設定になっているし、acts_as_...を身近に感じるので、aasmの方は今まで無視していた。
ところが、インストールしたacts_as_state_machineの更新日時を見てみると2006-11-13と相当古い...。1年半以上更新されていないことになる。(でも、特に不便は感じないのだけど。)一方、aasmのページを見てみると、今も活発に更新されている。最終更新日は2008-08-14になっている。どうやら、メンテされているのはaasmの方なので、こちらも試してみることにした。

aasmのインストール

aasmのページによると、gemで以下のようにインストールするようだ。

# ---------- ターミナルでコマンド入力 ----------
sudo gem sources -a http://gems.github.com
sudo gem install rubyist-aasm

aasmを有効にする

  • 以下のように--statefulオプションを利用してインストールしてみた。
# ---------- ターミナルでコマンド入力 ----------
rails todo_stateful
cd todo_stateful
script/generate scaffold todo body:string due:date done:boolean
script/plugin install git://github.com/technoweenie/restful-authentication.git
script/generate authenticated user sessions --stateful
rake db:migrate
  • その後、READMEやコメントに書いてある、お決まりの手順を実行する。
    • config/environment.rbに追記
      • config.active_record.observers = :user_observer
    • config.routes.rbに追記
      • map.activate '/activate/:activation_code', :controller => 'users', :action => 'activate', :activation_code => nil
      • map.resources :users, :member => { :suspend => :put, :unsuspend => :put, :purge => :delete }
    • app/controllers/application.rbに追記
      • include AuthenticatedSystem
  • この状態で、デフォルトではacts_as_state_machineをincludeするようになっているので、ここでaasmをincludeする設定にした。
# ---------- app/models/user.rb ----------
require 'digest/sha1'

class User < ActiveRecord::Base
  include Authentication
  include Authentication::ByPassword
  include Authentication::ByCookieToken
  #include Authorization::StatefulRoles
  include Authorization::AasmRoles
...(中略)...
  • これで、試しにscript/serverを実行してい見ると...見事に失敗した。
    • app/models/user.rbの8行目、include Authorization::AasmRolesのところでエラーが発生している。
"in `qualified_const_defined?': Plugins::Restful-authentication::Lib::Authorization" is not a valid constant name! (NameError)
  • どうもプラグインが有効になっていないようなので、restful_authenticationのinit.rbを確認してみると...
require File.dirname(__FILE__) + '/lib/authentication'
require File.dirname(__FILE__) + '/lib/authentication/by_password'
require File.dirname(__FILE__) + '/lib/authentication/by_cookie_token'
  • あっ、/lib/authorization/aasm_roles.rbがrequireされていない...。ということで以下を追記。
    • require File.dirname(__FILE__) + '/lib/authorization/aasm_roles'
  • 再度、script/serverを実行してみるが、またしても失敗した。
in `load_missing_constant': uninitialized constant Authorization::AasmRoles::AASM (NameError)
  • aasmも有効になっていないようなので、最終的には以下の設定にした。
require File.dirname(__FILE__) + '/lib/authentication'
require File.dirname(__FILE__) + '/lib/authentication/by_password'
require File.dirname(__FILE__) + '/lib/authentication/by_cookie_token'
require 'aasm'
require File.dirname(__FILE__) + '/lib/authorization/aasm_roles'

これでサーバーが起動した!

イベント、保存、コールバックのタイミングを確認

  • 状態を遷移するイベントが発生した時、どのようなタイミングで関連する処理が実行されるか確認してみた。以下は実験用state_machineの設定。
  before_save :do_before_save
  after_save  :do_after_save

  aasm_column :state
  aasm_initial_state :initial => :pending
  aasm_state :active,    :enter => :do_activate, :exit  => :do_exit_active
  aasm_state :suspended, :enter => :do_enter, :after => :do_after, :exit=>:do_exit_suspended

  aasm_event :suspend do
    transitions :from => [:passive, :pending, :active, :suspended], :to => :suspended, 
                :guard => Proc.new {puts "*"*40 + "guard"; false;}, 
                :on_transition => :do_on_transition
  end

  def do_enter
    puts "*"*40 + "enter"
  end
      
  def do_after
    puts "*"*40 + "after"
  end

  def do_exit_active
    puts "*"*40 + "exit_active"
  end

  def do_exit_suspended
    puts "*"*40 + "exit_suspended"
  end
      
  def do_before_save
    puts "*"*40 + "before_save"
  end
      
  def do_after_save
    puts "*"*40 + "after_save"
  end

  def do_on_transition
    puts "*"*40 + "do_on_transition"
  end
  • script/consoleで実験してみると...
#:activeから:suspendedの状態遷移を試してみる
>> @user.suspend!
 ****************************************exit_active
 ****************************************guard
 ****************************************do_on_transition
 ****************************************enter
 ****************************************before_save
 ****************************************after_save

# :suspendedから:suspendedの状態遷移を試してみる(同じ状態が継続するevent)
>> @user.suspend!
 ****************************************exit_suspended
 ****************************************guard
 ****************************************do_on_transition
 ****************************************enter
 ****************************************before_save
 ****************************************after_save

# :guardオプションをfalseで終了するブロックにして、:activeから:suspendedの状態遷移を試してみる
>> @user.suspend!
 ****************************************exit_active
 ****************************************guard

acts_as_state_machineとの違いとか、理解したこと

以上の実験結果から、aasmの仕様を以下のように理解した。

  • 新たに、eventによって状態遷移する途中で処理される:on_transitionが追加された。(acts_as_state_machineを利用していた時、とても欲しい機能だった。)
  • :afterオプションは削除された。
  • :exit、:guardの順で実行され、よってeventが発生すると、常に:exit、:guardは実行されることになる。
  • 同じ状態が継続するeventも同じ基準で問題なく処理される。(acts_as_state_machineは何もしないでtrueだけ返す。)
  • :exit、:guard:、on_transition、:enterでの処理は、その後saveされるので、実験の場合@userの変更はすべてDBに保存される。
# AからB、またはBからBへ状態遷移する時の処理の流れ
#   1. aasm_state :exitオプションの処理
#   2. aasm_eventブロックのtransitions :guardオプションの処理
#------:guardオプションの処理がfalseならここで終了。trueなら継続。------
#   3. aasm_eventブロックのtransitions :on_transitionオプションの処理
#   4. aasm_state :enterオプションの処理
#   5. ActiveRecord::Base.before_save
#   6. ActiveRecord::Base.save
#   7. ActiveRecord::Base.after_save
#                                                    guard
#                                                      |
#                                     +--on_transition-|--+
#                                     |                |  |
#                                     |                   |
# +-------+                           |  +-------------+  |
# |   A   |    guard                  |  |      B      |  |
# |       |      |                    v  | before_save |  |
# |       |exit -|-on_transition--> enter| save        |exit
# |       |      |                       | after_save  |
# |       |                              |             |
# +-------+                              +-------------+
#

なんとaasmは、自分がacts_as_state_machineに求める機能を既に実装してくれていたのだった!