AASMのReadmeを日本語訳してみた
AASMはRubyのクラスにステートマシンパターンを追加できるライブラリです。
業務で使うことがあったので訳してみました。
初の試みなので、おかしい点などがあればばしばし指摘してください。
元ページ
aasm/aasm · GitHub
AASM - Rubyのステートマシン
このパッケージにはRubyのクラスに有限なステートマシンを追加するライブラリであるAASMが含まれています。
AASMはacts_as_state_machineプラグインとして産声をあげましたが、ActiveRecord以外をターゲットにした汎用的なライブラリへと進化してきました。
現在はActiveRecordとMongoidへの接続を提供していますが、それらを親クラスに持たないどんなRubyのクラスへも適用することができます。
使用方法
AASMのモジュールをincludeするのと同じくらい簡単にステートマシンを追加し、stateやeventおよびそれらの間のtransitionsを設定することができます。
class Job include AASM aasm do state :sleeping, :initial => true state :running state :cleaning event :run do transitions :from => :sleeping, :to => :running end event :clean do transitions :from => :running, :to => :cleaning end event :sleep do transitions :from => [:running, :cleaning], :to => :sleeping end end end
このコードは Job クラスのインスタンスのpublic methodsの組を提供しています。
job = Job.new job.sleeping? # => true job.may_run? # => true job.run job.running? # => true job.sleeping? # => false job.may_run? # => false job.run # => raises AASM::InvalidTransition
例外を発生させずに true と false を返り値として使用する場合は下のコードのようにwhiny_transitionsにfalseを設定したハッシュを渡します。
class Job ... aasm :whiny_transitions => false do ... end end job.running? # => true job.may_run? # => false job.run # => false
コールバック
コールバックを定義すると、ある状態に遷移した時などの特定の条件を満たした場合に呼び出されます。
class Job include AASM aasm do state :sleeping, :initial => true, :before_enter => :do_something state :running event :run, :after => :notify_somebody do transitions :from => :sleeping, :to => :running, :on_transition => Proc.new {|obj, *args| obj.set_process(*args) } end event :sleep do after do ... end error do |e| ... end transitions :from => :running, :to => :sleeping end end def set_process(name) ... end def do_something ... end def notify_somebody ... end end
このケースでは do_something は状態が sleeping になる前に呼ばれます。
一方で、notify_somebody は run イベントの遷移( sleeping から running )が終了した後に呼ばれます。
使用できるコールバックと呼び出される順序は下の通りです。
event:before previous_state:before_exit new_state:before_enter ...update state... previous_state:after_exit new_state:after_enter event:after
下のようにしてeventにパラメータを渡すこともできます。
job = Job.new job.run(:running, :defragmentation)
このケースでは、 set_process は :defagmentation を引数として呼び出されます。
イベント実行中に例外が発生した場合には、その例外は捕捉され :error コールバックに渡されます。その中で処理するか、上位のクラスに伝搬することができます。
ガード条件
ある条件のもとでのみ許可したい状態遷移を実現するために、状態遷移ごとにガード条件を設けることができます。ガード条件は、実際に遷移が発生する前に判定されます。ガード条件がfalseを返した場合は状態遷移が拒否されます。(AASM::InvalidTransition が発生するか、falseが返ります)
class Job include AASM aasm do state :sleeping, :initial => true state :running state :cleaning event :run do transitions :from => :sleeping, :to => :running end event :clean do transitions :from => :running, :to => :cleaning end event :sleep do transitions :from => :running, :to => :sleeping, :guard => :cleaning_needed? end end def cleaning_needed? false end end job = Job.new job.run job.may_sleep? # => false job.sleep # => raises AASM::InvalidTransition
ActiveRecord
AASMはActiveRecordをサポートしており、データベース内のオブジェクトの状態を自動的に持続できます。
class Job < ActiveRecord::Base include AASM aasm do # default column: aasm_state state :sleeping, :initial => true state :running event :run do transitions :from => :sleeping, :to => :running end event :sleep do transitions :from => :running, :to => :sleeping end end end
自動保存するか、保存しないままにしておくか選択することができます。
job = Job.new job.run # not saved job.run! # saved
保存する場合には Job クラスの全てのバリデーションが実行されます。
バリデーションを実行せずに状態を保存するのを明記したい場合は、簡単に実装できます。(それによって異常な状態が保存されてしまうかもしれません)
class Job < ActiveRecord::Base include AASM aasm :skip_validation_on_save => true do state :sleeping, :initial => true state :running event :run do transitions :from => :sleeping, :to => :running end event :sleep do transitions :from => :running, :to => :sleeping end end end
スコープの自動生成
AASMはモデルの各stateに対して自動でスコープを作成します。
class Job < ActiveRecord::Base include AASM aasm do state :sleeping, :initial => true state :running state :cleaning end def sleeping "This method name is in already use" end end
class JobsController < ApplicationController def index @running_jobs = jobs.running @recent_cleaning_jobs = jobs.cleaning.where('created_at >= ?', 3.days.ago) # @sleeping_jobs = jobs.sleeping #=> "This method name is in already use" end end
トランザクションのサポート
バージョン3.0.13からActiveRecordのトランザクションをサポートしています。コールバックや状態遷移が失敗するたびにデータベースへの変更内容はロールバックされます。
カラム名とマイグレーション
デフォルトでは aasm_state に状態を保存します。下のように :column を使用すると、好きな列名に状態を保存できます。
class Job < ActiveRecord::Base include AASM aasm :column => 'my_state' do ... end end
状態を保存するカラムを必ずマイグレーションに追加してください。(列の型はstringです)
class AddJobState < ActiveRecord::Migration def self.up add_column :jobs, :aasm_state, :string end def self.down remove_column :job, :aasm_state end end