Skip to content

Instantly share code, notes, and snippets.

@imajes
Forked from jbarnette/example.rb
Created April 6, 2011 15:21
Show Gist options
  • Save imajes/905845 to your computer and use it in GitHub Desktop.
Save imajes/905845 to your computer and use it in GitHub Desktop.
require "stateful"
class Folder < ActiveRecord::Base
include Stateful
# ...
stateful do
state :active
state :inactive
state :deleted
on :activate do
move :inactive => :active
end
on :deactivate do
move :active => :inactive
end
on :delete do
move any => :deleted
end
entering :deleted do
children.each { |c| c.delete! unless c.deleted? }
end
entering :inactive do
children.each { |c| c.deactivate! if c.active? }
end
end
# Provides `active`, `inactive`, and `deleted` scopes and
# `active?`-style predicates, plus `activate!`-style transition
# helpers.
end
# Mark an ActiveRecord model as having a state machine. Requires a
# string attribute called `state`. To set the default state for a
# class, set a default column value for `state` in the database.
#
# Use Symbols for all keys and values in state definitions.
module Stateful
def self.included model
model.validates :state, presence: true
model.extend Macro
end
end
module Macro
def machine
@machine ||= Machine.new self
end
def stateful &block
machine.surrender &block
end
end
module Surrender
def surrender target = nil, &block
if block
target ||= self
block.arity == 0 ? target.instance_eval(&block) : yield(target)
end
end
end
class Event
ANY = Object.new
include Surrender
def initialize model, name
@moves = {}
model.send :define_method, "#{name}!" do |*args|
model.machine.fire self, name, *args
end
end
def any
ANY
end
def dest target
@moves[target.to_sym] || @moves[any]
end
def move pair
Array(pair.keys.first).each do |s|
@moves[s] = pair.values.first
end
end
end
class Machine
include Surrender
def initialize model
@model = model
@entering = Hash.new { |h, k| h[k] = [] }
@events = Hash.new { |h, k| h[k] = Event.new @model, k }
end
def entering name, &block
@entering[name] << block
end
def fire instance, name, *args
raise "No [#{name}] event." unless @events.include? name
dest = @events[name].dest instance.state
unless dest
raise "Can't [#{name}] while [#{instance.state}]: #{instance}"
end
@entering[dest].each do |hook|
surrender instance, &hook
end
instance.state = dest.to_s
instance.save!
end
def on name, &block
@events[name].surrender(&block)
end
def state name
name = name.to_s
@model.scope name, @model.where(state: name)
@model.send(:define_method, "#{name}?") { name == state }
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment