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,938