Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
# Usage:
#
# class Mail
# include SimpleStateMachine
#
# self.initial_state = 'unread'
# self.transitions_map = {
# read: {from: 'unread', to: 'read'},
# unread: {from: 'any', to: 'unread'},
# delete: {from: 'any', to: 'deleted'},
# spam: {from: 'any', to: 'spam'}
# }
#
# attr_accessor :state
# end
#
# mail = Mail.new
# mail.state # => 'unread'
# mail.read # => true
# mail.state # => 'read'
# mail.read_allowed? # => false
# mail.read! # => RuntimeError: Invalid transition from `read` by `read`
#
module SimpleStateMachine
def state
fail NotImplementedError
end
def state=(value)
fail NotImplementedError
end
def initialize(*)
super
self.state = self.class.initial_state if self.class.initial_state
end
def self.included(klass)
klass.extend ClassMethods
end
module ClassMethods
attr_accessor :initial_state
def transitions_map=(transitions_map)
transitions_map.each do |transition_name, options|
from = Array(options.fetch(:from))
to = options.fetch(:to)
define_method("#{transition_name}_allowed?") do
!([state, 'any'] & from).empty?
end
define_method(transition_name) do
return unless send("#{transition_name}_allowed?")
self.state = to
true
end
define_method("#{transition_name}!") do
return true if send(transition_name)
raise "Invalid transition from `#{state}` by `#{transition_name}`"
end
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment