zakihayaメモ

RubyとRailsのことが中心

AASMのReadmeを日本語訳してみた

AASMはRubyのクラスにステートマシンパターンを追加できるライブラリです。
業務で使うことがあったので訳してみました。

初の試みなので、おかしい点などがあればばしばし指摘してください。

元ページ
aasm/aasm · GitHub

AASM - Rubyのステートマシン

このパッケージにはRubyのクラスに有限なステートマシンを追加するライブラリであるAASMが含まれています。

AASMはacts_as_state_machineプラグインとして産声をあげましたが、ActiveRecord以外をターゲットにした汎用的なライブラリへと進化してきました。
現在はActiveRecordとMongoidへの接続を提供していますが、それらを親クラスに持たないどんなRubyのクラスへも適用することができます。

使用方法

AASMのモジュールをincludeするのと同じくらい簡単にステートマシンを追加し、stateeventおよびそれらの間の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