Skip to content

Instantly share code, notes, and snippets.

@mihar
Last active December 30, 2015 23:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mihar/7f6503e02f5431b4dd4e to your computer and use it in GitHub Desktop.
Save mihar/7f6503e02f5431b4dd4e to your computer and use it in GitHub Desktop.
A simple ActiveRecord state machine for Mongoid
module SimpleStateMachine
# Usage:
#
# First, extend your model of choise with this module
#
# extend SimpleStateMachine
#
# Then you can call the method states() and pass all the states
# that your model supports. E.g.:
#
# states('ready', 'finished')
#
# This will create the following methods:
#
# Model.ready?, Model.ready!
# Model.finished, Model.finished!
#
# And define the following fields:
#
# Model.status (string)
# Model.ready_at (time)
# Model.finished_at (time)
#
# If you don't want to create the status field, for example
# if it's already being used for something else, you can pass
# false as the first parameter to the states() method, like this:
#
# states(false, 'ready', 'finished')
#
# The default is 'created' or the first passed in status if 'created' is not available
#
def states(*states)
use_status_field = true
if states.first.is_a?(Boolean)
use_status_field = states.shift
end
if use_status_field
default_status = 'created'
default_status = states.first unless default_status.in?(states)
field :status, type: String, default: default_status
validates :status, presence: true, inclusion: { in: states }
end
states.each { |s| state s, use_status_field } # Pass the use_status_field along.
define_method :status_at do
if read_attribute("#{status}_at")
read_attribute("#{status}_at")
else
read_attribute("updated_at")
end
end
end
def state(state, use_status_field, &_block)
field "#{state}_at", type: Time
scope "not_#{state}", -> { self.or({ :"#{state}_at".gte => Time.now }, { :"#{state}_at" => nil }) }
scope state, -> { where(:"#{state}_at".lte => Time.now) }
define_method "#{state}?" do
!!(read_attribute("#{state}_at") && read_attribute("#{state}_at") <= Time.now)
end
define_method "#{state}" do
write_attribute "#{state}_at", Time.now
if use_status_field
write_attribute :status, state
end
end
define_method "#{state}!" do
return if send("#{state}?")
send(state) # Call the method w/o the bang.
save!
yield if block_given?
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment