state_manager 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.document +5 -0
- data/Gemfile +18 -0
- data/Gemfile.lock +74 -0
- data/LICENSE.txt +20 -0
- data/README.md +241 -0
- data/Rakefile +45 -0
- data/VERSION +1 -0
- data/lib/state_manager/adapters/active_record.rb +52 -0
- data/lib/state_manager/adapters/base.rb +49 -0
- data/lib/state_manager/adapters.rb +30 -0
- data/lib/state_manager/base.rb +169 -0
- data/lib/state_manager/core.rb +7 -0
- data/lib/state_manager/dsl.rb +77 -0
- data/lib/state_manager/helpers.rb +47 -0
- data/lib/state_manager/plugins/delayed_job.rb +54 -0
- data/lib/state_manager/plugins.rb +4 -0
- data/lib/state_manager/resource.rb +83 -0
- data/lib/state_manager/state.rb +146 -0
- data/lib/state_manager.rb +1 -0
- data/state_manager.gemspec +102 -0
- data/test/adapters/active_record_test.rb +112 -0
- data/test/basic_test.rb +132 -0
- data/test/definition_test.rb +132 -0
- data/test/helper.rb +29 -0
- data/test/helpers_test.rb +61 -0
- data/test/plugins/delayed_job_test.rb +131 -0
- data/test/transitions_test.rb +171 -0
- metadata +226 -0
@@ -0,0 +1,169 @@
|
|
1
|
+
module StateManager
|
2
|
+
|
3
|
+
class StateNotFound < StandardError; end;
|
4
|
+
class InvalidEvent < StandardError; end;
|
5
|
+
class InvalidTransition < StandardError; end;
|
6
|
+
|
7
|
+
# The base StateManager class is responsible for tracking the current state
|
8
|
+
# of an object as well as managing the transitions between states.
|
9
|
+
class Base < State
|
10
|
+
|
11
|
+
class_attribute :_resource_class
|
12
|
+
class_attribute :_resource_name
|
13
|
+
class_attribute :_state_property
|
14
|
+
self._state_property = :state
|
15
|
+
|
16
|
+
attr_accessor :resource, :context
|
17
|
+
|
18
|
+
def initialize(resource, context={})
|
19
|
+
super(nil, nil)
|
20
|
+
self.resource = resource
|
21
|
+
self.context = context
|
22
|
+
|
23
|
+
transition_to(initial_state.path) unless current_state
|
24
|
+
end
|
25
|
+
|
26
|
+
# Transitions to the state at the specified path. The path can be relative
|
27
|
+
# to any state along the current state's path.
|
28
|
+
def transition_to(path)
|
29
|
+
path = path.to_s
|
30
|
+
state = current_state || self
|
31
|
+
exit_states = []
|
32
|
+
|
33
|
+
# Find the nearest parent state on the path of the current state which
|
34
|
+
# has a sub-state at the given path
|
35
|
+
new_states = state.find_states(path)
|
36
|
+
while(!new_states) do
|
37
|
+
exit_states << state
|
38
|
+
state = state.parent_state
|
39
|
+
raise(StateNotFound, path) unless state
|
40
|
+
new_states = state.find_states(path)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Can only transition to leaf states
|
44
|
+
# TODO: transition to the initial_state of the state?
|
45
|
+
raise(InvalidTransition, path) unless new_states.last.leaf?
|
46
|
+
|
47
|
+
enter_states = new_states - exit_states
|
48
|
+
exit_states = exit_states - new_states
|
49
|
+
|
50
|
+
from_state = current_state
|
51
|
+
to_state = enter_states.last
|
52
|
+
|
53
|
+
# Before Callbacks
|
54
|
+
will_transition(from_state, to_state, current_event)
|
55
|
+
exit_states.each{ |s| s.exit }
|
56
|
+
enter_states.each{ |s| s.enter }
|
57
|
+
|
58
|
+
# Set the state on the underlying resource
|
59
|
+
self.current_state = to_state
|
60
|
+
|
61
|
+
# After Callbacks
|
62
|
+
exit_states.each{ |s| s.exited }
|
63
|
+
enter_states.each{ |s| s.entered }
|
64
|
+
did_transition(from_state, to_state, current_event)
|
65
|
+
end
|
66
|
+
|
67
|
+
def current_state
|
68
|
+
path = read_state
|
69
|
+
find_state(path) if path && !path.empty?
|
70
|
+
end
|
71
|
+
|
72
|
+
def current_state=(value)
|
73
|
+
write_state(value)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Send an event to the current state.
|
77
|
+
#
|
78
|
+
# Unlike the regular send_event method, this method recursively walks the
|
79
|
+
# path of states starting at the current state.
|
80
|
+
def send_event!(name, *args)
|
81
|
+
self.current_event = name
|
82
|
+
state = find_state_for_event(name)
|
83
|
+
raise(InvalidEvent, name) unless state
|
84
|
+
state.send_event name, *args
|
85
|
+
self.current_event = nil
|
86
|
+
end
|
87
|
+
|
88
|
+
def respond_to_event?(name)
|
89
|
+
!!find_state_for_event(name)
|
90
|
+
end
|
91
|
+
|
92
|
+
def find_state_for_event(name)
|
93
|
+
state = current_state
|
94
|
+
while(state) do
|
95
|
+
return state if state.has_event?(name)
|
96
|
+
state = state.parent_state
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def state_manager
|
101
|
+
self
|
102
|
+
end
|
103
|
+
|
104
|
+
def to_s
|
105
|
+
"#{current_state.path}" if current_state
|
106
|
+
end
|
107
|
+
|
108
|
+
# Returns true if the underlying object is in the state specified by the
|
109
|
+
# given path. An object is 'in' a state if the state lies at any point of
|
110
|
+
# the current state's path. E.g:
|
111
|
+
#
|
112
|
+
# state_manager.current_state.path # returns 'outer.inner'
|
113
|
+
# state_manager.in_state? 'outer' # true
|
114
|
+
# state_manager.in_state? 'outer.inner' # true
|
115
|
+
# state_manager.in_state? 'inner' # false
|
116
|
+
#
|
117
|
+
def in_state?(path)
|
118
|
+
self.find_states(current_state.path).include? find_state(path)
|
119
|
+
end
|
120
|
+
|
121
|
+
# These methods can be overriden by an adapter
|
122
|
+
def write_state(value)
|
123
|
+
resource.send "#{self.class._state_property.to_s}=", value.path
|
124
|
+
end
|
125
|
+
|
126
|
+
def read_state
|
127
|
+
resource.send self.class._state_property
|
128
|
+
end
|
129
|
+
|
130
|
+
def will_transition(from, to, event)
|
131
|
+
end
|
132
|
+
|
133
|
+
def did_transition(from, to, event)
|
134
|
+
end
|
135
|
+
|
136
|
+
# All events the current state will respond to
|
137
|
+
def available_events
|
138
|
+
state = current_state
|
139
|
+
ret = {}
|
140
|
+
while(state) do
|
141
|
+
ret = state.class.specification.events.merge(ret)
|
142
|
+
state = state.parent_state
|
143
|
+
end
|
144
|
+
ret
|
145
|
+
end
|
146
|
+
|
147
|
+
def self.infer_resource_name!
|
148
|
+
return if _resource_name
|
149
|
+
if name =~ /States/
|
150
|
+
self._resource_name = name.demodulize.gsub(/States/, '').underscore
|
151
|
+
create_resource_accessor!(_resource_name)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def self.inherited(base)
|
156
|
+
super(base)
|
157
|
+
base.infer_resource_name!
|
158
|
+
end
|
159
|
+
|
160
|
+
def self.added_to_resource(klass, property, options)
|
161
|
+
end
|
162
|
+
|
163
|
+
protected
|
164
|
+
|
165
|
+
attr_accessor :current_event
|
166
|
+
|
167
|
+
end
|
168
|
+
|
169
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'active_support/core_ext'
|
2
|
+
|
3
|
+
module StateManager
|
4
|
+
module DSL
|
5
|
+
|
6
|
+
module State
|
7
|
+
# Specifies a state that is a child of the current state
|
8
|
+
def state(name, klass=nil, &block)
|
9
|
+
# If no base class is specified we look for a class inside the current
|
10
|
+
# state's class which has the same name as the state
|
11
|
+
const_name = name.capitalize
|
12
|
+
klass ||= if const_defined?(const_name)
|
13
|
+
self.const_get(name.capitalize)
|
14
|
+
else
|
15
|
+
Class.new(StateManager::State)
|
16
|
+
end
|
17
|
+
klass = Class.new(klass, &block) if block_given?
|
18
|
+
|
19
|
+
remove_const const_name if const_defined?(const_name)
|
20
|
+
const_set(const_name, klass)
|
21
|
+
|
22
|
+
specification.states[name.to_sym] = klass
|
23
|
+
end
|
24
|
+
|
25
|
+
# Specifies an event on the current state
|
26
|
+
def event(name, options={}, &block)
|
27
|
+
name = name.to_sym
|
28
|
+
event = options.dup
|
29
|
+
event[:name] = name
|
30
|
+
specification.events[name] = event
|
31
|
+
define_method name, &block if block_given?
|
32
|
+
end
|
33
|
+
|
34
|
+
# Helper to simplify creating dsl reader methods for specification
|
35
|
+
# properties
|
36
|
+
module_eval do
|
37
|
+
def self.spec_property(name)
|
38
|
+
class_eval do
|
39
|
+
define_method name do |value|
|
40
|
+
specification.send "#{name}=", value
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# The initial state
|
47
|
+
def initial_state(value)
|
48
|
+
specification.initial_state = value
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
module Base
|
53
|
+
def resource_class(value)
|
54
|
+
self._resource_class = value
|
55
|
+
end
|
56
|
+
|
57
|
+
def resource_name(value)
|
58
|
+
self._resource_name = value
|
59
|
+
create_resource_accessor!(_resource_name)
|
60
|
+
end
|
61
|
+
|
62
|
+
def state_property(value)
|
63
|
+
self._state_property = value
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
class State
|
70
|
+
extend DSL::State
|
71
|
+
end
|
72
|
+
|
73
|
+
class Base
|
74
|
+
extend DSL::Base
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module StateManager
|
2
|
+
# State helper methods. Examples:
|
3
|
+
#
|
4
|
+
# @post.event! # send_event! :event
|
5
|
+
# @post.active? # in_state? :active
|
6
|
+
# @post.can_event? # respond_to_event? :event
|
7
|
+
#
|
8
|
+
module Helpers
|
9
|
+
|
10
|
+
module Methods
|
11
|
+
def self.define_methods(specification, target_class, property)
|
12
|
+
self.define_methods_helper(specification, target_class, [], property)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.define_methods_helper(specification, target_class, name_parts, property)
|
16
|
+
sm_proc = Proc.new do
|
17
|
+
self.send "#{property}_manager"
|
18
|
+
end
|
19
|
+
|
20
|
+
specification.events.each do |name, event|
|
21
|
+
target_class.send :define_method, "#{name.to_s}!" do | *args |
|
22
|
+
state_manager = instance_eval &sm_proc
|
23
|
+
state_manager.send_event! name, *args
|
24
|
+
end
|
25
|
+
|
26
|
+
target_class.send :define_method, "can_#{name.to_s}?" do
|
27
|
+
state_manager = instance_eval &sm_proc
|
28
|
+
state_manager.respond_to_event?(name)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
specification.states.each do |name, child_class|
|
33
|
+
state_name_parts = name_parts.dup << name
|
34
|
+
method = state_name_parts.join('_')
|
35
|
+
path = state_name_parts.join('.')
|
36
|
+
target_class.send :define_method, "#{method}?" do
|
37
|
+
state_manager = instance_eval &sm_proc
|
38
|
+
state_manager.in_state?(path)
|
39
|
+
end
|
40
|
+
|
41
|
+
define_methods_helper(child_class.specification, target_class, state_name_parts, property)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
if defined?(Delayed)
|
2
|
+
|
3
|
+
module StateManager
|
4
|
+
# Adds support for a :delay property on event definitions. Events with a
|
5
|
+
# delay set will be automatically sent after the delay. If the state is
|
6
|
+
# changed such that the event is no longer available before the delay is
|
7
|
+
# reached, it will be canceled.
|
8
|
+
module DelayedJob
|
9
|
+
|
10
|
+
class DelayedEvent < Struct.new(:path, :event, :state_manager)
|
11
|
+
def perform
|
12
|
+
return unless state_manager.respond_to_event?(event[:name]) &&
|
13
|
+
state_manager.in_state?(path)
|
14
|
+
state_manager.send_event! event[:name]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module State
|
19
|
+
|
20
|
+
def delayed_events
|
21
|
+
self.class.specification.events.reject{|name,event|!event[:delay]}
|
22
|
+
end
|
23
|
+
|
24
|
+
def entered
|
25
|
+
delayed_events.each do |name, event|
|
26
|
+
delay = event[:delay]
|
27
|
+
delayed_event = DelayedEvent.new(path, event, state_manager)
|
28
|
+
Delayed::Job.enqueue delayed_event, :run_at => delay.from_now
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def exited
|
33
|
+
# TODO: we currently just have logic inside the job itself which
|
34
|
+
# skips the event if it is no longer relevant. This is not perfect.
|
35
|
+
# Ideally we should cancel events in this method (requiring an
|
36
|
+
# efficient way to do this without looping over all events).
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class State
|
42
|
+
include DelayedJob::State
|
43
|
+
|
44
|
+
def entered
|
45
|
+
super
|
46
|
+
end
|
47
|
+
|
48
|
+
def exited
|
49
|
+
super
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module StateManager
|
2
|
+
module Resource
|
3
|
+
|
4
|
+
def self.extended(base)
|
5
|
+
base.instance_eval do
|
6
|
+
class_attribute :state_managers
|
7
|
+
self.state_managers = {}
|
8
|
+
|
9
|
+
attr_accessor :state_managers
|
10
|
+
end
|
11
|
+
|
12
|
+
base.send :include, InstanceMethods
|
13
|
+
end
|
14
|
+
|
15
|
+
def state_manager(property=:state, klass=nil, options={}, &block)
|
16
|
+
default_options = {:helpers => true}
|
17
|
+
options = default_options.merge(options)
|
18
|
+
|
19
|
+
klass ||= begin
|
20
|
+
"#{self.name}States".constantize
|
21
|
+
rescue NameError
|
22
|
+
nil
|
23
|
+
end
|
24
|
+
klass ||= StateManager::Base
|
25
|
+
|
26
|
+
# Create a subclass of the specified state manager and mixin an adapter
|
27
|
+
# if a matching one is found
|
28
|
+
this = self
|
29
|
+
adapter = Adapters.match(self)
|
30
|
+
resource_name = self.name.demodulize.underscore
|
31
|
+
|
32
|
+
klass = Class.new(klass) do
|
33
|
+
state_property property
|
34
|
+
resource_class this
|
35
|
+
resource_name resource_name
|
36
|
+
include adapter.const_get('ManagerMethods') if adapter
|
37
|
+
class_eval &block if block_given?
|
38
|
+
end
|
39
|
+
include adapter.const_get('ResourceMethods') if adapter
|
40
|
+
|
41
|
+
# Callbacks
|
42
|
+
state_manager_added(property, klass, options) if respond_to? :state_manager_added
|
43
|
+
klass.added_to_resource(self, property, options)
|
44
|
+
|
45
|
+
# Define the subclass as a constant. We do this for multiple reasons, one
|
46
|
+
# of which is to allow it to be serialized to YAML for delayed_job
|
47
|
+
const_name = "#{property.to_s.camelize}States"
|
48
|
+
remove_const const_name if const_defined?(const_name)
|
49
|
+
const_set(const_name, klass)
|
50
|
+
|
51
|
+
# Create an accessor for the state manager on this resource
|
52
|
+
state_managers[property] = klass
|
53
|
+
property_name = "#{property.to_s}_manager"
|
54
|
+
define_method property_name do
|
55
|
+
self.state_managers ||= {}
|
56
|
+
state_manager = state_managers[property]
|
57
|
+
unless state_manager
|
58
|
+
state_manager = klass.new(self)
|
59
|
+
state_managers[property] = state_manager
|
60
|
+
end
|
61
|
+
state_manager
|
62
|
+
end
|
63
|
+
|
64
|
+
# Define the helper methods on the resource
|
65
|
+
Helpers::Methods.define_methods(klass.specification, self, property) if options[:helpers]
|
66
|
+
end
|
67
|
+
|
68
|
+
module InstanceMethods
|
69
|
+
# Ensures that all properties with state managers are in valid states
|
70
|
+
def validate_states!
|
71
|
+
self.state_managers ||= {}
|
72
|
+
self.class.state_managers.each do |name, klass|
|
73
|
+
# Simply ensuring that all of the state managers have been
|
74
|
+
# instantiated will make the corresponding states valid
|
75
|
+
unless state_managers[name]
|
76
|
+
state_managers[name] = klass.new(self)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
require 'active_support/core_ext'
|
2
|
+
|
3
|
+
module StateManager
|
4
|
+
class State
|
5
|
+
|
6
|
+
# Represents the static specification of this state. This consists of all
|
7
|
+
# child states and events. During initialization, the specification will
|
8
|
+
# be read and the child states and events will be initialized.
|
9
|
+
class Specification
|
10
|
+
attr_accessor :states, :events, :initial_state
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
self.states = {}
|
14
|
+
self.events = {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize_copy(source)
|
18
|
+
self.states = source.states.dup
|
19
|
+
self.events = source.events.dup
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class_attribute :specification
|
24
|
+
self.specification = Specification.new
|
25
|
+
|
26
|
+
def self.inherited(child)
|
27
|
+
# Give all sublcasses a clone of this states specification. Subclasses can
|
28
|
+
# add events and states to their specification without affecting the
|
29
|
+
# parent
|
30
|
+
child.specification = specification.clone
|
31
|
+
end
|
32
|
+
|
33
|
+
attr_reader :name, :states, :parent_state
|
34
|
+
|
35
|
+
def initialize(name, parent_state)
|
36
|
+
self.name = name
|
37
|
+
self.parent_state = parent_state
|
38
|
+
self.states = self.class.specification.states.inject({}) do |states, (name, klazz)|
|
39
|
+
states[name] = klazz.new(name, self)
|
40
|
+
states
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# String representing the path of the current state, e.g.:
|
45
|
+
# 'parentState.childState'
|
46
|
+
def path
|
47
|
+
path = name.to_s
|
48
|
+
path = "#{parent_state.path}.#{path}" if parent_state && parent_state.name
|
49
|
+
path
|
50
|
+
end
|
51
|
+
|
52
|
+
def enter
|
53
|
+
end
|
54
|
+
|
55
|
+
def exit
|
56
|
+
end
|
57
|
+
|
58
|
+
def entered
|
59
|
+
end
|
60
|
+
|
61
|
+
def exited
|
62
|
+
end
|
63
|
+
|
64
|
+
def to_s
|
65
|
+
"#{path}"
|
66
|
+
end
|
67
|
+
|
68
|
+
def to_sym
|
69
|
+
path.to_sym
|
70
|
+
end
|
71
|
+
|
72
|
+
def state_manager
|
73
|
+
parent_state.state_manager
|
74
|
+
end
|
75
|
+
|
76
|
+
# Get the resource stored on the state manager
|
77
|
+
def resource
|
78
|
+
state_manager.resource
|
79
|
+
end
|
80
|
+
|
81
|
+
def transition_to(*args)
|
82
|
+
state_manager.transition_to(*args)
|
83
|
+
end
|
84
|
+
|
85
|
+
def has_event?(name)
|
86
|
+
name = name.to_sym
|
87
|
+
!!self.class.specification.events[name]
|
88
|
+
end
|
89
|
+
|
90
|
+
def send_event(name, *args)
|
91
|
+
name = name.to_sym
|
92
|
+
event = self.class.specification.events[name]
|
93
|
+
send(name, *args) if respond_to?(name)
|
94
|
+
transition_to(event[:transitions_to]) if event[:transitions_to]
|
95
|
+
end
|
96
|
+
|
97
|
+
# Find all the states along the path
|
98
|
+
def find_states(path)
|
99
|
+
state = self
|
100
|
+
parts = path.split('.')
|
101
|
+
ret = [state]
|
102
|
+
parts.each do |name|
|
103
|
+
state = state.states[name.to_sym]
|
104
|
+
ret << state
|
105
|
+
return unless state
|
106
|
+
end
|
107
|
+
ret
|
108
|
+
end
|
109
|
+
|
110
|
+
# Returns the state at the given path
|
111
|
+
def find_state(path)
|
112
|
+
states = find_states(path)
|
113
|
+
states && states.last
|
114
|
+
end
|
115
|
+
|
116
|
+
def leaf?
|
117
|
+
states.empty?
|
118
|
+
end
|
119
|
+
|
120
|
+
# If an initial state is not explicitly specified, we choose the first leaf
|
121
|
+
# state
|
122
|
+
def initial_state
|
123
|
+
if state = self.class.specification.initial_state
|
124
|
+
find_state(state.to_s)
|
125
|
+
elsif leaf?
|
126
|
+
self
|
127
|
+
else
|
128
|
+
states.values.first.initial_state
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def self.create_resource_accessor!(name)
|
133
|
+
unless method_defined?(name)
|
134
|
+
define_method name do
|
135
|
+
resource
|
136
|
+
end
|
137
|
+
end
|
138
|
+
specification.states.values.each {|s|s.create_resource_accessor!(name)}
|
139
|
+
end
|
140
|
+
|
141
|
+
protected
|
142
|
+
|
143
|
+
attr_writer :name, :states, :parent_state
|
144
|
+
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'state_manager/core'
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "state_manager"
|
8
|
+
s.version = "0.2.3"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Gordon Hempton"]
|
12
|
+
s.date = "2012-06-06"
|
13
|
+
s.description = "Finite state machine implementation that keeps logic separate from model classes and supports sub-states."
|
14
|
+
s.email = "ghempton@gmail.com"
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE.txt",
|
17
|
+
"README.md"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
"Gemfile",
|
22
|
+
"Gemfile.lock",
|
23
|
+
"LICENSE.txt",
|
24
|
+
"README.md",
|
25
|
+
"Rakefile",
|
26
|
+
"VERSION",
|
27
|
+
"lib/state_manager.rb",
|
28
|
+
"lib/state_manager/adapters.rb",
|
29
|
+
"lib/state_manager/adapters/active_record.rb",
|
30
|
+
"lib/state_manager/adapters/base.rb",
|
31
|
+
"lib/state_manager/base.rb",
|
32
|
+
"lib/state_manager/core.rb",
|
33
|
+
"lib/state_manager/dsl.rb",
|
34
|
+
"lib/state_manager/helpers.rb",
|
35
|
+
"lib/state_manager/plugins.rb",
|
36
|
+
"lib/state_manager/plugins/delayed_job.rb",
|
37
|
+
"lib/state_manager/resource.rb",
|
38
|
+
"lib/state_manager/state.rb",
|
39
|
+
"state_manager.gemspec",
|
40
|
+
"test/adapters/active_record_test.rb",
|
41
|
+
"test/basic_test.rb",
|
42
|
+
"test/definition_test.rb",
|
43
|
+
"test/helper.rb",
|
44
|
+
"test/helpers_test.rb",
|
45
|
+
"test/plugins/delayed_job_test.rb",
|
46
|
+
"test/transitions_test.rb"
|
47
|
+
]
|
48
|
+
s.homepage = "http://github.com/ghempton/statemanager"
|
49
|
+
s.licenses = ["MIT"]
|
50
|
+
s.require_paths = ["lib"]
|
51
|
+
s.rubygems_version = "1.8.15"
|
52
|
+
s.summary = "%Q{Finite state machine implementation.}"
|
53
|
+
|
54
|
+
if s.respond_to? :specification_version then
|
55
|
+
s.specification_version = 3
|
56
|
+
|
57
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
58
|
+
s.add_runtime_dependency(%q<activesupport>, [">= 0"])
|
59
|
+
s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
|
60
|
+
s.add_development_dependency(%q<pry>, [">= 0"])
|
61
|
+
s.add_development_dependency(%q<pry-doc>, [">= 0"])
|
62
|
+
s.add_development_dependency(%q<pry-remote>, [">= 0"])
|
63
|
+
s.add_development_dependency(%q<pry-nav>, [">= 0"])
|
64
|
+
s.add_development_dependency(%q<pry-stack_explorer>, [">= 0"])
|
65
|
+
s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
|
66
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.8.3"])
|
67
|
+
s.add_development_dependency(%q<delayed_job_active_record>, [">= 0"])
|
68
|
+
s.add_development_dependency(%q<activerecord>, [">= 0"])
|
69
|
+
s.add_development_dependency(%q<sqlite3>, [">= 0"])
|
70
|
+
s.add_development_dependency(%q<timecop>, [">= 0"])
|
71
|
+
else
|
72
|
+
s.add_dependency(%q<activesupport>, [">= 0"])
|
73
|
+
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
74
|
+
s.add_dependency(%q<pry>, [">= 0"])
|
75
|
+
s.add_dependency(%q<pry-doc>, [">= 0"])
|
76
|
+
s.add_dependency(%q<pry-remote>, [">= 0"])
|
77
|
+
s.add_dependency(%q<pry-nav>, [">= 0"])
|
78
|
+
s.add_dependency(%q<pry-stack_explorer>, [">= 0"])
|
79
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
80
|
+
s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
|
81
|
+
s.add_dependency(%q<delayed_job_active_record>, [">= 0"])
|
82
|
+
s.add_dependency(%q<activerecord>, [">= 0"])
|
83
|
+
s.add_dependency(%q<sqlite3>, [">= 0"])
|
84
|
+
s.add_dependency(%q<timecop>, [">= 0"])
|
85
|
+
end
|
86
|
+
else
|
87
|
+
s.add_dependency(%q<activesupport>, [">= 0"])
|
88
|
+
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
89
|
+
s.add_dependency(%q<pry>, [">= 0"])
|
90
|
+
s.add_dependency(%q<pry-doc>, [">= 0"])
|
91
|
+
s.add_dependency(%q<pry-remote>, [">= 0"])
|
92
|
+
s.add_dependency(%q<pry-nav>, [">= 0"])
|
93
|
+
s.add_dependency(%q<pry-stack_explorer>, [">= 0"])
|
94
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
95
|
+
s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
|
96
|
+
s.add_dependency(%q<delayed_job_active_record>, [">= 0"])
|
97
|
+
s.add_dependency(%q<activerecord>, [">= 0"])
|
98
|
+
s.add_dependency(%q<sqlite3>, [">= 0"])
|
99
|
+
s.add_dependency(%q<timecop>, [">= 0"])
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|