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
- config/environment.rbに追記
- この状態で、デフォルトでは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に求める機能を既に実装してくれていたのだった!