Last active
December 30, 2015 23:28
-
-
Save mihar/7f6503e02f5431b4dd4e to your computer and use it in GitHub Desktop.
A simple ActiveRecord state machine for Mongoid
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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