Saturday, October 10, 2009

Chaining state transitions with Acts As State Machine (aasm)

Have you ever had state transition requirements that required automatic chaining of transition changes? I've got a system that transitions to a "to_reviewer" state after it has been created.

In the "to_reviewer" state, a group of reviewers are presented the assignment and they may accept the assignment, or a project manager may assign the assignment to a reviewer: in either case, as soon as there is a reviewer assigned, the assignment transitions to the "reviewer" state.

However, if the system already has a reviewer assigned at the "created" event, then it should cascade the state transition change and go directly to "reviewer" without staying in "to_reviewer"

Having selected the Rubyist AASM gem to implement my state machine in this Ruby on Rails project, I expected that I would be able to use the :after options of the aasm_state statement. I got that idea from the README.rdoc for version 2.1.1, which lists "event:after" in the callback chain.

However, the :after callback is called on an event (aasm_event) and I can't find documentation on how to hook into that callback. Digging into the the code, I found the following syntax works correctly in my Assignment object (derived from ActiveRecord). The solution is to use the :after_event option in the aasm_state definition statement.

This first part defines the states:
aasm_initial_state :task_created
aasm_state :task_created

aasm_state :to_reviewer, :after_enter => :check_reviewer
aasm_state :reviewer

This next part defines the events:
aasm_event :created do
transitions :to => :to_reviewer, :from => [:task_created]
end

aasm_event :task_review do
transitions :to => :reviewer, :from => [:to_reviewer]
end

This last part is the definition of the :after_enter method:
def check_task_reviewer
task_review unless reviewer_id.blank?
end


Now, when the object is created, the aasm_current_state is "task_created". If the object receives the "created" event, it transitions to the "to_reviewer" state. However, after the object enters the "to_reviewer" state, the "check_task_reviewer" method advances the state again if a reviewer as been assigned (indicated by the presence of the reviewer_id).

Checking the code in the console, we get:

>> a = Assignment.new
=> (Assignment object information)
>> a.reviewer_id = 5
=> 5
>> a.save
=> true
>> a.created!
=> true
>> a.aasm_current_state
=> :reviewer

And we can see that the object transitioned through the "to_reviewer" state an on into the "reviewer"state.

No comments:

Post a Comment