state_manager 0.2.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|