a machine

state of mind

@vaidehijoshi

tough problems

things are never what they seem

endless

learning

daunting

🙀

state machines

finite state machines

directed graph

?

...

?!

they're everywhere

control how

things happen

four parts of

a state machine

🔧

1. things that need to get done

functions

2. reasons to call the functions

events

3. some data to keep track of these functions

states

4. do some work before an event or state change 

code in our functions

state
status
Order.first.status
#=> "shipped"

👎

instance methods
boolean values
Order.first.shipped?
#=> true

👎

timestamps with
a NULL value
Order.first.paid_at
#=> nil

👎

records valid by
a period of time
Membership.first.start_date 
#=> 2016-03-01 23:59:00 -0700

Membership.first.end_date 
#=> 2016-04-01 23:59:00 -0700

👎

order = Order.first
order.submitted?
order.mark_as_completed
order.ship

okay, but where do we start?

1. functions

user inputing a number to transition from unplaced to submitted

2. events

user successfully submitting the form, updating the database

3. states

an order in the processing state includes fulfilling, packaging, shipping functionality

4. code in functions

checking if an order's products are in_stock? before an event transitions from unsubmitted to processing state

order.completed
order.processing
order.returned

self-referential

directed cyclic graphs

enough with the theory

what does this even look like?

gem 'aasm'
acts_as_state_machine
statesman
state_machine
class Order
  include AASM
end
initial state
state to transition to
class Order
  include AASM

  aasm do
    state :unplaced, initial: true
    state :submitted
    
    event :submit do
      transitions from: :unplaced, to: :submitted
    end
  end
end
class Order
  include AASM

  aasm do
    state :unplaced, initial: true
    state :submitted
    state :processing
    state :shipped

    event :submit do
      transitions from: :unplaced, to: :submitted
    end

    event :process do
      transitions from: :submitted, to: :processing
    end

    event :ship do
      transitions from: :processing, to: :shipped
    end
  end
end
class Order
  include AASM

  aasm do
    state :unplaced, initial: true
    state :submitted
    state :processing
    state :shipped
    state :completed
    state :returned
    state :deleted

    event :submit do
      transitions from: :unplaced, to: :submitted
    end

    event :process do
      transitions from: [:submitted, :returned], to: :processing
    end

    event :ship do
      transitions from: :processing, to: :shipped
    end

    event :complete do
      transitions from: :shipped, to: :completed
    end

    event :return do
      transitions from: :complete, to: :returned
    end

    event :delete do
      transitions from: :processing, to: :deleted
    end
  end
end
event :process do
  transitions from: [:submitted, :returned], to: :processing
end

the process event looks back to states to determine whether it can continue or not

order = Order.new       # => <Order:0x007fad3d51aa30>

order.unplaced?         # => true
order.may_submit?       # => true
order.submit!           # triggers the submit event
order.submitted?        # => true
order.unplaced?         # => false
order.may_submit?       # => false
order.submit!           # => raises AASM::InvalidTransition

wait.

we didn't write those methods!

module AASM
  class Base
    safely_define_method klass, "may_#{name}?", ->(*args) do
       aasm(aasm_name).may_fire_event?(event, *args)
    end
  end
end
order.may_submit?
# => true

what if we didn't have a state machine?

1. Write a migration that adds a string "state" or "status" column for our Order object

2. Give our state column an initial value of "unplaced"

3. Add an instance method "unplaced?" with a boolean return value

to create an unplaced state

4. Add another instance method called "submit" that changes the object's state from "unplaced" to "submitted"

5. Add another instance called "submitted?"

6. cry

tips and tricks

pass a block to an event

def order_shipped_email
  # Sends an email informing the User
  # that their Order has been shipped
end

order.ship do
  order.send_order_shipped_email
end

# Only if order.may_ship? returns true
# will the send_order_shipped_email
# method actually fire

use a callback

state :shipped, before_enter: :print_return_label
state :deleted

event :ship do
  transitions from: :processing, to: :shipped
end

event :delete do, after: :send_delete_confirmation do
  transitions from: :processing, to: :deleted
end

def print_return_label
  # Prints order details along with
  # the return label information.
end

def send_delete_confirmation
  # Sends a confirmation email
  # that the Order has been deleted.
end

implement a guard

event :submit do
  transitions from: :unplaced, to: :submitted, 
    guard: :payment_successfully_processed?
end

def payment_successfully_processed?
  # Returns a truthy value based on
  # whether the user's credit card info
  # has been processed successfully or not
end

return booleans instead of exceptions

aasm whiny_transitions: false do

end

learning new things can be scary

intimidating

seem impossible

but that doesn't mean you can't do it.

thanks!

@vaidehijoshi

a machine state of mind

By Vaidehi Joshi

a machine state of mind

  • 1,955