Search code examples
ruby-on-railsruby-on-rails-3state-machineaasm

AASM state machine exception handling example?


I'm currently working on a class, which is basically doing following:

  • model gets created
  • fetches data (event "get_things!")
    • if exception happens, state should become "failed"
    • if success, state should be "finished"

I try to implement it as following:

class Fetcher < ActiveRecord::Base
  include AASM

  aasm do
    state :created, initial: true
    state :success, :failed

    event :succeed do
      transitions from: :created, to: :success
    end

    event :fail do
      transitions from: :created, to: :failed
    end
  end

  def read_things!(throw_exception = false)
    begin
      raise RuntimeError.new("RAISED EXCEPTION") if throw_exception
      self.content = open("https://example.com?asd=324").read
      self.succeed!
    rescue => e
      self.fail!
    end
  end
end

a = Fetcher.new
a.read_things!(throw_exception = true)
=> state should be failed

a = Fetcher.new
a.read_things!(throw_exception = false)
=> state should be succeess

It works, but looks somehow not really good to do...

I would prefer something like the error handling which is mentioned in the readme

event :read_things do
  before do
    self.content = open("https://example.com?asd=324").read
    self.succeed!
  end
  error do |e|
    self.fail!
  end
  transitions :from => :created, :to => :success
end

but I dont know if this is really the best practice here?

I also have many events, which all should behave like my mentioned error handling is showing above and I saw that I can somehow use error_on_all_events - but didnt find any documentation about it?

Any thoughts? Thanks!

edit: Changed some small parts to remove confusion.


Solution

  • First, is the method name fetch! or read_things? In any case, you don't want to pass a boolean argument to determine whether or not to raise an exception. If an exception is raised then your rescue will pick it up:

    def read_things
      self.content = open("https://example.com?asd=324").read
      succeed!
    rescue => e
      # do something with the error (e.g. log it), otherwise remove the "=> e"
      fail!
    end
    

    I would prefer something like the error handling which is mentioned in the readme

    Your error handling example is actually good practice (with a few minor edits):

    event :read_things do
      before do
        self.content = open("https://example.com?asd=324").read
      end
      error do |e|
        fail! # the self is optional (since self is your Fetcher object)
      end
      transitions from: :created, to: :success
    end
    

    In practice:

    a = Fetcher.new
    a.read_things!
    

    If self.content doesn't raise an exception then the state will transition from created to success (no need to call succeed! directly), otherwise the error handler will call the fail! transition, which will attempt to transition the state to failed.

    EDIT:

    Example usage of error_on_all_events callback with AASM:

    aasm do
      error_on_all_events :handle_error_for_all_events
    
      state :created, initial: true
      state :success, :failed
    
      event :succeed do
        transitions from: :created, to: :success
      end
    
      event :fail do
        transitions from: :created, to: :failed
      end
    end
    
    def handle_error_for_all_events
      # inspect the aasm object and handle error...
      puts aasm
    end