Skip to content

Embed URL

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
require "overlord/searchable"
module Overlord
module Stateful
ANY = Object.new
def self.included(model)
model.validates :state, presence: true
model.extend Macro
model.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
end
if model < Overlord::Searchable
Sunspot.setup model do
string :state, stored: true
end
end
end
module Macro
def machine
@machine ||= Machine.new self
end
def stateful(&block)
machine.instance_eval(&block)
end
end
class Context < Struct.new(:instance, :event, :src, :dest, :args)
def trigger(hooks)
hooks.values_at(dest, ANY).compact.flatten.each do |hook|
instance.instance_exec self, &hook
end
end
end
class Event
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 current
@moves[current.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
def initialize model
@model = model
@entered = Hash.new { |h, k| h[k] = [] }
@entering = Hash.new { |h, k| h[k] = [] }
@events = Hash.new { |h, k| h[k] = Event.new @model, k }
@persisted = Hash.new { |h, k| h[k] = [] }
end
def entered(*names, &block)
munge(names).each { |n| @entered[n] << block }
end
def entering(*names, &block)
munge(names).each { |n| @entering[n] << block }
end
def fire(instance, name, *args)
raise "No [#{name}] event." unless @events.include? name
src = instance.state
dest = @events[name].dest src
unless dest
raise "Can't [#{name}] while [#{instance.state}]: #{instance}"
end
ctx = Context.new instance, name, src.to_sym, dest, args
ActiveRecord::Base.transaction do
ctx.trigger @entering
instance.state = ctx.dest.to_s
ctx.trigger @entered
instance.save!
end
ctx.trigger @persisted
instance
end
def on(*names, &block)
names.flatten.each { |n| @events[n].instance_eval(&block) }
end
def persisted(*names, &block)
munge(names).each { |n| @persisted[n] << block}
end
def state(name)
name = name.to_s
unless @model.respond_to? name
@model.scope name, @model.where(state: name)
@model.send(:define_method, "#{name}?") { name == state }
end
end
private
def munge(names)
names.flatten!
if names.empty? then [ANY] else names end
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.