aasm/README_FROM_VERSION_3_TO_4.md

4.5 KiB

Migrating from AASM version 3 to 4

Must

Callback order has been changed

The first callback to be run is :before of the event. A state's :before_exit callback is now run directly before its :exit callback. Event-based guards are now run before any of the transition guards are run. And finally, before running any state callbacks, all (event- and transition-based) guards are run to check whether the state callbacks can be run or not.

Callback :on_transition renamed to :after and changed its binding

The transition callback :on_transition has been renamed to :after in order to make clear it is being called (namely after doing the transition).

Furthermore, in alignment with the other callbacks, it's not receiving the object at hand as first parameter and binds the current object to self.

In summary, change from

  aasm do
    ...
      transitions :from => :from_state, :to => :to_state, :on_transition => :do_something
    ...
  end

  ...
  def some_other_method(arg)
    ...
  end

  def do_something(obj, arg1, arg2)
    obj.some_other_method(arg1)
  end

to

  aasm do
    ...
      transitions :from => :from_state, :to => :to_state, :after => :do_something
    ...
  end

  ...
  def some_other_method(arg)
    ...
  end

  def do_something(arg1, arg2)
    some_other_method(arg1) # run on the object as self
  end

after_commit hooks are now event-based

The after_commit hooks have been move from the state level to the event level. So, if you want some code block to be executed after the AASM state has been saved AND committed, change this code

class Job < ActiveRecord::Base
  include AASM

  aasm do
    state :sleeping, :initial => true
    state :running, :after_commit => :notify_about_running_job

    event :run do
      transitions :from => :sleeping, :to => :running
    end
  end

  def notify_about_running_job
    ...
  end
end

to

class Job < ActiveRecord::Base
  include AASM

  aasm do
    state :sleeping, :initial => true
    state :running

    event :run, :after_commit => :notify_about_running_job do
      transitions :from => :sleeping, :to => :running
    end
  end

  def notify_about_running_job
    ...
  end
end

Instance-level inspection

Listing events for the current state now returns Event objects instead of event names (as symbols). So, change from

job = Job.new

job.aasm.events
# => [:run]

to

job = Job.new

job.aasm.events.map(&:name)
# => [:run]

Retrieving the list of permitted events has now been integrated into the events method. Change from

job = Job.new

job.aasm.permissible_events
# => [:run]

to

job = Job.new

job.aasm.events(:permitted => true).map(&:name)
# => [:run]

Class-based events now return a list of Event instances. Change from

Job.aasm.events.values.map(&:name)
# => [:run]

to

Job.aasm.events.map(&:name)
# => [:run]

Could

Triggering an event without to_state

When providing parameters to callbacks it is not required to provide the to_state anymore. So, assuming you have the following class:

class Job
  include AASM

  aasm do
    state :sleeping, :initial => true
    state :running

    event :run do
      transitions :from => :sleeping, :to => :running, :after => :log
    end
  end

  def log(message)
    logger.info message
  end
end

then you could change from

job = Job.new
job.run(:running, "we want to run")

to this:

job = Job.new
job.run("we want to run")

job.run(:running, "we want to run") # still supported to select the target state (the _to_state_)

On the other hand, you have to accept the arguments for all callback methods (and procs) you provide and use. If you don't want to provide these, you can splat them

def before(*args); end
# or
def before(*_); end # to indicate that you don't want to use the arguments

New configuration option: no_direct_assignment

If you want to make sure that the AASM column for storing the state is not directly assigned, configure AASM to not allow direct assignment, like this:

class Job < ActiveRecord::Base
  include AASM

  aasm :no_direct_assignment => true do
    state :sleeping, :initial => true
    state :running

    event :run do
      transitions :from => :sleeping, :to => :running
    end
  end

end

resulting in this:

job = Job.create
job.aasm_state # => 'sleeping'
job.aasm_state = :running # => raises AASM::NoDirectAssignmentError
job.aasm_state # => 'sleeping'