simple_state_machine 0.5.3 → 0.6.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +102 -78
- data/examples/user.rb +7 -4
- data/lib/simple_state_machine/active_record.rb +46 -32
- data/lib/simple_state_machine/railtie.rb +1 -1
- data/lib/simple_state_machine/simple_state_machine.rb +122 -23
- data/lib/simple_state_machine/version.rb +1 -1
- data/lib/tasks/graphviz.rake +21 -0
- data/simple_state_machine.gemspec +1 -0
- data/spec/active_record_spec.rb +37 -0
- data/spec/mountable_spec.rb +14 -4
- data/spec/simple_state_machine_spec.rb +106 -77
- data/spec/state_machine_definition_spec.rb +96 -7
- data/spec/state_machine_spec.rb +34 -0
- metadata +28 -12
- data/lib/tasks/graphiz.rake +0 -13
- data/rails/graphiz.rake +0 -16
data/README.rdoc
CHANGED
@@ -5,34 +5,18 @@ A simple DSL to decorate existing methods with state transition guards.
|
|
5
5
|
Instead of using a DSL to define events, SimpleStateMachine decorates methods
|
6
6
|
to help you encapsulate state and guard state transitions.
|
7
7
|
|
8
|
-
It supports exception rescuing, google chart visualization and mountable
|
8
|
+
It supports exception rescuing, google chart visualization and mountable state machines.
|
9
9
|
|
10
|
-
== Basic example
|
11
10
|
|
12
|
-
|
11
|
+
== Installation
|
13
12
|
|
14
|
-
|
13
|
+
Use gem install:
|
15
14
|
|
16
|
-
|
17
|
-
self.state = 'off'
|
18
|
-
end
|
15
|
+
gem install simple_state_machine
|
19
16
|
|
20
|
-
|
21
|
-
puts "pushed switch"
|
22
|
-
end
|
23
|
-
event :push_switch, :off => :on,
|
24
|
-
:on => :off
|
25
|
-
|
26
|
-
end
|
17
|
+
Or add it to your Gemfile:
|
27
18
|
|
28
|
-
|
29
|
-
lamp.state # => 'off'
|
30
|
-
lamp.off? # => true
|
31
|
-
lamp.push_switch # => 'pushed switch'
|
32
|
-
lamp.state # => 'on'
|
33
|
-
lamp.on? # => true
|
34
|
-
lamp.push_switch # => 'pushed switch'
|
35
|
-
lamp.off? # => true
|
19
|
+
gem 'simple_state_machine'
|
36
20
|
|
37
21
|
|
38
22
|
== Basic usage
|
@@ -53,59 +37,55 @@ That's it!
|
|
53
37
|
You can now call activate_account and the state will automatically change.
|
54
38
|
If the state change is not allowed, a SimpleStateMachine::IllegalStateTransitionError is raised.
|
55
39
|
|
56
|
-
=== Using ActiveRecord / ActiveModel validations
|
57
|
-
When using ActiveRecord / ActiveModel you can add an error to the errors object.
|
58
|
-
This will prevent the state from being changed.
|
59
|
-
|
60
|
-
def activate_account(activation_code)
|
61
|
-
if activation_code_invalid?(activation_code)
|
62
|
-
errors.add(:activation_code, 'Invalid')
|
63
|
-
end
|
64
|
-
end
|
65
40
|
|
66
|
-
|
41
|
+
== Basic example
|
67
42
|
|
68
|
-
|
69
|
-
You can rescue exceptions and specify the failure state
|
43
|
+
class LampSwitch
|
70
44
|
|
71
|
-
|
72
|
-
Service.download_data
|
73
|
-
end
|
74
|
-
event :download_data, :pending => :downloaded,
|
75
|
-
Service::ConnectionError => :download_failed
|
45
|
+
extend SimpleStateMachine
|
76
46
|
|
77
|
-
|
78
|
-
|
47
|
+
def initialize
|
48
|
+
self.state = 'off'
|
49
|
+
end
|
79
50
|
|
80
|
-
|
81
|
-
|
51
|
+
def push_switch
|
52
|
+
puts "pushed switch"
|
53
|
+
end
|
54
|
+
event :push_switch, :off => :on,
|
55
|
+
:on => :off
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
lamp = LampSwitch.new
|
60
|
+
lamp.state # => 'off'
|
61
|
+
lamp.off? # => true
|
62
|
+
lamp.push_switch # => 'pushed switch'
|
63
|
+
lamp.state # => 'on'
|
64
|
+
lamp.on? # => true
|
65
|
+
lamp.push_switch # => 'pushed switch'
|
66
|
+
lamp.off? # => true
|
82
67
|
|
83
|
-
event :suspend, :all => :suspended
|
84
68
|
|
85
69
|
== ActiveRecord Example
|
86
70
|
|
87
|
-
To add a state machine
|
71
|
+
To add a state machine to an ActiveRecord class, you will have to:
|
88
72
|
- extend SimpleStateMachine::ActiveRecord,
|
89
73
|
- set the initial state in after_initialize,
|
90
74
|
- turn methods into events
|
91
75
|
|
92
76
|
class User < ActiveRecord::Base
|
93
|
-
|
77
|
+
|
94
78
|
extend SimpleStateMachine::ActiveRecord
|
95
79
|
|
96
80
|
def after_initialize
|
97
|
-
self.
|
98
|
-
|
99
|
-
|
100
|
-
# write_attribute(:ssm_state, "pending") unless read_attribute(:ssm_state)
|
101
|
-
end
|
102
|
-
|
81
|
+
self.state ||= 'pending'
|
82
|
+
end
|
83
|
+
|
103
84
|
def invite
|
104
85
|
self.activation_code = Digest::SHA1.hexdigest("salt #{Time.now.to_f}")
|
105
|
-
#send_activation_email
|
106
86
|
end
|
107
87
|
event :invite, :pending => :invited
|
108
|
-
|
88
|
+
|
109
89
|
def confirm_invitation activation_code
|
110
90
|
if self.activation_code != activation_code
|
111
91
|
errors.add 'activation_code', 'is invalid'
|
@@ -115,9 +95,10 @@ To add a state machine with ActiveRecord persistence:
|
|
115
95
|
end
|
116
96
|
|
117
97
|
This generates the following event methods
|
118
|
-
- invite
|
119
|
-
- invite!
|
120
|
-
- confirm_invitation
|
98
|
+
- invite (behaves like ActiveRecord save )
|
99
|
+
- invite! (behaves like ActiveRecord save!)
|
100
|
+
- confirm_invitation (behaves like ActiveRecord save )
|
101
|
+
- confirm_invitation! (behaves like ActiveRecord save!)
|
121
102
|
|
122
103
|
And the following methods to query the state:
|
123
104
|
- pending?
|
@@ -125,40 +106,82 @@ And the following methods to query the state:
|
|
125
106
|
- active?
|
126
107
|
|
127
108
|
If you want to be more verbose you can also use:
|
128
|
-
- invite_and_save (
|
129
|
-
- invite_and_save! (
|
109
|
+
- invite_and_save (alias for invite)
|
110
|
+
- invite_and_save! (alias for invite!)
|
130
111
|
|
131
|
-
== Mountable Example
|
132
112
|
|
133
|
-
|
113
|
+
== ActiveRecord Mountable Example
|
134
114
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
115
|
+
If you like to separate your state machine from your model class, you can do so as following:
|
116
|
+
|
117
|
+
class MyStateMachine < SimpleStateMachine::StateMachineDefinition
|
118
|
+
|
119
|
+
event(:invite, :new => :invited)
|
120
|
+
event(:confirm_invitation, :invited => :active)
|
121
|
+
|
122
|
+
def decorator_class
|
123
|
+
SimpleStateMachine::Decorator
|
141
124
|
end
|
125
|
+
end
|
142
126
|
|
143
|
-
|
144
|
-
|
145
|
-
extend SimpleStateMachine::Mountable
|
146
|
-
self.state_machine_definition = MyStateMachine.new self
|
127
|
+
class User < ActiveRecord::Base
|
147
128
|
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
129
|
+
extend SimpleStateMachine::Mountable
|
130
|
+
mount_state_machine MyStateMachine
|
131
|
+
|
132
|
+
def after_initialize
|
133
|
+
self.state ||= 'new'
|
152
134
|
end
|
153
135
|
|
136
|
+
end
|
154
137
|
|
155
|
-
== Generating google chart visualizations
|
156
138
|
|
157
|
-
|
139
|
+
== Using ActiveRecord / ActiveModel validations
|
140
|
+
|
141
|
+
When using ActiveRecord / ActiveModel you can add an error to the errors object:
|
142
|
+
|
143
|
+
def activate_account(activation_code)
|
144
|
+
if activation_code_invalid?(activation_code)
|
145
|
+
errors.add(:activation_code, 'Invalid')
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
activate_account!("INVALID_CODE") # => ActiveRecord::RecordInvalid, "Validation failed: Activation code is invalid"
|
150
|
+
|
151
|
+
This will prevent the state from being changed.
|
152
|
+
|
153
|
+
|
154
|
+
== Catching exceptions
|
155
|
+
|
156
|
+
You can let the state machine handle exceptions by specifying the failure state for an Error:
|
157
|
+
|
158
|
+
def download_data
|
159
|
+
Service.download_data
|
160
|
+
end
|
161
|
+
event :download_data, :pending => :downloaded,
|
162
|
+
Service::ConnectionError => :download_failed
|
163
|
+
|
164
|
+
download_data # catches Service::ConnectionError
|
165
|
+
state # => "download_failed"
|
166
|
+
state_machine.raised_error # the raised error
|
167
|
+
|
168
|
+
== Catching all from states
|
169
|
+
If an event should transition from all other defined states, you can use :all as from state:
|
170
|
+
|
171
|
+
event :suspend, :all => :suspended
|
172
|
+
|
173
|
+
|
174
|
+
== Generating state diagrams
|
175
|
+
|
176
|
+
When using Rails/ActiveRecord you can generate a state diagram of the state machine via the
|
177
|
+
built in rake tasks.
|
178
|
+
For details run:
|
179
|
+
|
180
|
+
rake -T ssm
|
158
181
|
|
159
182
|
|
160
183
|
== Note on Patches/Pull Requests
|
161
|
-
|
184
|
+
|
162
185
|
* Fork the project.
|
163
186
|
* Make your feature addition or bug fix.
|
164
187
|
* Add tests for it. This is important so I don't break it in a
|
@@ -167,6 +190,7 @@ If your using rails you get rake tasks for generating a graphiz google chart of
|
|
167
190
|
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
168
191
|
* Send me a pull request. Bonus points for topic branches.
|
169
192
|
|
193
|
+
|
170
194
|
== Copyright
|
171
195
|
|
172
196
|
Copyright (c) 2010 Marek & Petrik. See LICENSE for details.
|
data/examples/user.rb
CHANGED
@@ -1,12 +1,15 @@
|
|
1
1
|
require 'digest/sha1'
|
2
2
|
class User < ActiveRecord::Base
|
3
|
-
|
3
|
+
|
4
4
|
validates_presence_of :name
|
5
|
-
|
5
|
+
|
6
6
|
extend SimpleStateMachine::ActiveRecord
|
7
7
|
|
8
8
|
def after_initialize
|
9
9
|
self.state ||= 'new'
|
10
|
+
# if you get an ActiveRecord::MissingAttributeError
|
11
|
+
# you'll probably need to do (http://bit.ly/35q23b):
|
12
|
+
# write_attribute(:ssm_state, "pending") unless read_attribute(:ssm_state)
|
10
13
|
end
|
11
14
|
|
12
15
|
def invite
|
@@ -23,13 +26,13 @@ class User < ActiveRecord::Base
|
|
23
26
|
event :confirm_invitation, :invited => :active
|
24
27
|
|
25
28
|
#event :log_send_activation_code_failed, :new => :send_activation_code_failed
|
26
|
-
#
|
29
|
+
#
|
27
30
|
# def reset_password(new_password)
|
28
31
|
# self.password = new_password
|
29
32
|
# end
|
30
33
|
# # do not change state, but ensure that we are in proper state
|
31
34
|
# event :reset_password, :active => :active
|
32
|
-
|
35
|
+
|
33
36
|
def send_activation_email(code)
|
34
37
|
true
|
35
38
|
end
|
@@ -14,46 +14,59 @@ module SimpleStateMachine::ActiveRecord
|
|
14
14
|
def decorate transition
|
15
15
|
super transition
|
16
16
|
event_name = transition.event_name.to_s
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
17
|
+
decorate_save event_name
|
18
|
+
decorate_save! event_name
|
19
|
+
end
|
20
|
+
|
21
|
+
protected
|
22
|
+
|
23
|
+
def decorate_save event_name
|
24
|
+
event_name_and_save = "#{event_name}_and_save"
|
25
|
+
unless @subject.method_defined?(event_name_and_save)
|
26
|
+
@subject.send(:define_method, event_name_and_save) do |*args|
|
27
|
+
result = false
|
28
|
+
old_state = self.send(self.class.state_machine_definition.state_method)
|
29
|
+
send "with_managed_state_#{event_name}", *args
|
30
|
+
if !self.errors.entries.empty?
|
29
31
|
self.send("#{self.class.state_machine_definition.state_method}=", old_state)
|
30
|
-
|
32
|
+
else
|
33
|
+
if save
|
34
|
+
result = true
|
35
|
+
else
|
36
|
+
self.send("#{self.class.state_machine_definition.state_method}=", old_state)
|
37
|
+
end
|
31
38
|
end
|
39
|
+
return result
|
32
40
|
end
|
41
|
+
@subject.send :alias_method, "#{event_name}", event_name_and_save
|
33
42
|
end
|
34
|
-
@subject.send :alias_method, "#{transition.event_name}", event_name_and_save
|
35
43
|
end
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
send
|
41
|
-
|
42
|
-
self.send(
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
44
|
+
|
45
|
+
def decorate_save! event_name
|
46
|
+
event_name_and_save_bang = "#{event_name}_and_save!"
|
47
|
+
unless @subject.method_defined?(event_name_and_save_bang)
|
48
|
+
@subject.send(:define_method, event_name_and_save_bang) do |*args|
|
49
|
+
result = nil
|
50
|
+
old_state = self.send(self.class.state_machine_definition.state_method)
|
51
|
+
send "with_managed_state_#{event_name}", *args
|
52
|
+
if !self.errors.entries.empty?
|
53
|
+
self.send("#{self.class.state_machine_definition.state_method}=", old_state)
|
54
|
+
raise ActiveRecord::RecordInvalid.new(self)
|
55
|
+
end
|
56
|
+
begin
|
57
|
+
result = save!
|
58
|
+
rescue ActiveRecord::RecordInvalid
|
59
|
+
self.send("#{self.class.state_machine_definition.state_method}=", old_state)
|
60
|
+
raise #re raise
|
61
|
+
end
|
62
|
+
return result
|
50
63
|
end
|
64
|
+
@subject.send :alias_method, "#{event_name}!", event_name_and_save_bang
|
51
65
|
end
|
52
|
-
@subject.send :alias_method, "#{transition.event_name}!", event_name_and_save_bang
|
53
66
|
end
|
54
|
-
end
|
55
67
|
|
56
|
-
|
68
|
+
def decorate_ar_method
|
69
|
+
end
|
57
70
|
|
58
71
|
def alias_event_methods event_name
|
59
72
|
@subject.send :alias_method, "without_managed_state_#{event_name}", event_name
|
@@ -68,7 +81,8 @@ module SimpleStateMachine::ActiveRecord
|
|
68
81
|
def state_machine_definition
|
69
82
|
unless @state_machine_definition
|
70
83
|
@state_machine_definition = SimpleStateMachine::StateMachineDefinition.new
|
71
|
-
@state_machine_definition.
|
84
|
+
@state_machine_definition.decorator_class = Decorator
|
85
|
+
@state_machine_definition.subject = self
|
72
86
|
end
|
73
87
|
@state_machine_definition
|
74
88
|
end
|
@@ -11,7 +11,7 @@ module SimpleStateMachine
|
|
11
11
|
def state_machine_definition
|
12
12
|
unless @state_machine_definition
|
13
13
|
@state_machine_definition = StateMachineDefinition.new
|
14
|
-
@state_machine_definition.
|
14
|
+
@state_machine_definition.subject = self
|
15
15
|
end
|
16
16
|
@state_machine_definition
|
17
17
|
end
|
@@ -22,6 +22,12 @@ module SimpleStateMachine
|
|
22
22
|
state_machine_definition.decorator.decorate(transition)
|
23
23
|
end
|
24
24
|
end
|
25
|
+
|
26
|
+
def mount_state_machine mountable_class
|
27
|
+
self.state_machine_definition = mountable_class.new
|
28
|
+
self.state_machine_definition.subject = self
|
29
|
+
self.state_machine_definition.add_events
|
30
|
+
end
|
25
31
|
end
|
26
32
|
include Mountable
|
27
33
|
|
@@ -31,11 +37,7 @@ module SimpleStateMachine
|
|
31
37
|
|
32
38
|
# mark the method as an event and specify how the state should transition
|
33
39
|
def event event_name, state_transitions
|
34
|
-
|
35
|
-
[froms].flatten.each do |from|
|
36
|
-
state_machine_definition.add_transition(event_name, from, to)
|
37
|
-
end
|
38
|
-
end
|
40
|
+
state_machine_definition.define_event event_name, state_transitions
|
39
41
|
end
|
40
42
|
|
41
43
|
end
|
@@ -58,16 +60,33 @@ module SimpleStateMachine
|
|
58
60
|
# Defines state machine transitions
|
59
61
|
class StateMachineDefinition
|
60
62
|
|
61
|
-
attr_writer :state_method, :
|
63
|
+
attr_writer :default_error_state, :state_method, :subject, :decorator,
|
64
|
+
:decorator_class
|
62
65
|
|
63
66
|
def decorator
|
64
|
-
@decorator ||= @
|
67
|
+
@decorator ||= decorator_class.new(@subject)
|
68
|
+
end
|
69
|
+
|
70
|
+
def decorator_class
|
71
|
+
@decorator_class ||= Decorator
|
72
|
+
end
|
73
|
+
|
74
|
+
def default_error_state
|
75
|
+
@default_error_state && @default_error_state.to_s
|
65
76
|
end
|
66
77
|
|
67
78
|
def transitions
|
68
79
|
@transitions ||= []
|
69
80
|
end
|
70
81
|
|
82
|
+
def define_event event_name, state_transitions
|
83
|
+
state_transitions.each do |froms, to|
|
84
|
+
[froms].flatten.each do |from|
|
85
|
+
add_transition(event_name, from, to)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
71
90
|
def add_transition event_name, from, to
|
72
91
|
transition = Transition.new(event_name, from, to)
|
73
92
|
transitions << transition
|
@@ -83,21 +102,88 @@ module SimpleStateMachine
|
|
83
102
|
transitions.map(&:to_s).join("\n")
|
84
103
|
end
|
85
104
|
|
86
|
-
|
87
|
-
|
88
|
-
|
105
|
+
module Graphviz
|
106
|
+
# Graphviz dot format for rendering as a directional graph
|
107
|
+
def to_graphviz_dot
|
108
|
+
transitions.map { |t| t.to_graphviz_dot }.sort.join(";")
|
109
|
+
end
|
110
|
+
|
111
|
+
# Generates a url that renders states and events as a directional graph.
|
112
|
+
# See http://code.google.com/apis/chart/docs/gallery/graphviz.html
|
113
|
+
def google_chart_url
|
114
|
+
"http://chart.googleapis.com/chart?cht=gv&chl=digraph{#{::CGI.escape to_graphviz_dot}}"
|
115
|
+
end
|
89
116
|
end
|
117
|
+
include Graphviz
|
118
|
+
|
119
|
+
module Inspector
|
120
|
+
def begin_states
|
121
|
+
from_states - to_states
|
122
|
+
end
|
123
|
+
|
124
|
+
def end_states
|
125
|
+
to_states - from_states
|
126
|
+
end
|
127
|
+
|
128
|
+
def states
|
129
|
+
(to_states + from_states).uniq
|
130
|
+
end
|
131
|
+
|
132
|
+
private
|
133
|
+
|
134
|
+
def from_states
|
135
|
+
to_uniq_sym(sample_transitions.map(&:from))
|
136
|
+
end
|
90
137
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
138
|
+
def to_states
|
139
|
+
to_uniq_sym(sample_transitions.map(&:to))
|
140
|
+
end
|
141
|
+
|
142
|
+
def to_uniq_sym(array)
|
143
|
+
array.map { |state| state.is_a?(String) ? state.to_sym : state }.uniq
|
144
|
+
end
|
145
|
+
|
146
|
+
def sample_transitions
|
147
|
+
(@subject || sample_subject).state_machine_definition.send :transitions
|
148
|
+
end
|
149
|
+
|
150
|
+
def sample_subject
|
151
|
+
self_class = self.class
|
152
|
+
sample = Class.new do
|
153
|
+
extend SimpleStateMachine::Mountable
|
154
|
+
mount_state_machine self_class
|
155
|
+
end
|
156
|
+
sample
|
157
|
+
end
|
95
158
|
end
|
159
|
+
include Inspector
|
160
|
+
|
161
|
+
module Mountable
|
162
|
+
def event event_name, state_transitions
|
163
|
+
events << [event_name, state_transitions]
|
164
|
+
end
|
165
|
+
|
166
|
+
def events
|
167
|
+
@events ||= []
|
168
|
+
end
|
169
|
+
|
170
|
+
module InstanceMethods
|
171
|
+
def add_events
|
172
|
+
self.class.events.each do |event_name, state_transitions|
|
173
|
+
define_event event_name, state_transitions
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
extend Mountable
|
179
|
+
include Mountable::InstanceMethods
|
180
|
+
|
96
181
|
end
|
97
182
|
|
98
183
|
##
|
99
184
|
# Defines the state machine used by the instance
|
100
185
|
class StateMachine
|
186
|
+
attr_reader :raised_error
|
101
187
|
|
102
188
|
def initialize(subject)
|
103
189
|
@subject = subject
|
@@ -116,13 +202,20 @@ module SimpleStateMachine
|
|
116
202
|
end
|
117
203
|
|
118
204
|
# Transitions to the next state if next_state exists.
|
205
|
+
# When an error occurs, it uses the error to determine next state.
|
206
|
+
# If no next state can be determined it transitions to the default error
|
207
|
+
# state if defined, otherwise the error is re-raised.
|
119
208
|
# Calls illegal_event_callback event_name if no next_state is found
|
120
209
|
def transition(event_name)
|
210
|
+
clear_raised_error
|
121
211
|
if to = next_state(event_name)
|
122
212
|
begin
|
123
213
|
result = yield
|
124
214
|
rescue => e
|
125
|
-
|
215
|
+
error_state = error_state(event_name, e) ||
|
216
|
+
state_machine_definition.default_error_state
|
217
|
+
if error_state
|
218
|
+
@raised_error = e
|
126
219
|
@subject.send("#{state_method}=", error_state)
|
127
220
|
return result
|
128
221
|
else
|
@@ -148,6 +241,10 @@ module SimpleStateMachine
|
|
148
241
|
|
149
242
|
private
|
150
243
|
|
244
|
+
def clear_raised_error
|
245
|
+
@raised_error = nil
|
246
|
+
end
|
247
|
+
|
151
248
|
def state_machine_definition
|
152
249
|
@subject.class.state_machine_definition
|
153
250
|
end
|
@@ -184,18 +281,17 @@ module SimpleStateMachine
|
|
184
281
|
|
185
282
|
# returns true if it's a error transition for event_name and error
|
186
283
|
def is_error_transition_for?(event_name, error)
|
187
|
-
is_same_event?(event_name) &&
|
284
|
+
is_same_event?(event_name) && from.is_a?(Class) && error.is_a?(from)
|
188
285
|
end
|
189
286
|
|
190
287
|
def to_s
|
191
288
|
"#{from}.#{event_name}! => #{to}"
|
192
289
|
end
|
193
290
|
|
194
|
-
def
|
291
|
+
def to_graphviz_dot
|
195
292
|
%("#{from}"->"#{to}"[label=#{event_name}])
|
196
293
|
end
|
197
294
|
|
198
|
-
|
199
295
|
private
|
200
296
|
|
201
297
|
def is_same_event?(event_name)
|
@@ -214,12 +310,13 @@ module SimpleStateMachine
|
|
214
310
|
attr_writer :subject
|
215
311
|
def initialize(subject)
|
216
312
|
@subject = subject
|
313
|
+
end
|
314
|
+
|
315
|
+
def decorate transition
|
217
316
|
define_state_machine_method
|
218
317
|
define_state_getter_method
|
219
318
|
define_state_setter_method
|
220
|
-
end
|
221
319
|
|
222
|
-
def decorate transition
|
223
320
|
define_state_helper_method(transition.from)
|
224
321
|
define_state_helper_method(transition.to)
|
225
322
|
define_event_method(transition.event_name)
|
@@ -229,8 +326,10 @@ module SimpleStateMachine
|
|
229
326
|
private
|
230
327
|
|
231
328
|
def define_state_machine_method
|
232
|
-
|
233
|
-
@
|
329
|
+
unless any_method_defined?("state_machine")
|
330
|
+
@subject.send(:define_method, "state_machine") do
|
331
|
+
@state_machine ||= StateMachine.new(self)
|
332
|
+
end
|
234
333
|
end
|
235
334
|
end
|
236
335
|
|
@@ -0,0 +1,21 @@
|
|
1
|
+
namespace :ssm do
|
2
|
+
namespace :graph do
|
3
|
+
desc 'Generate a url for a google chart. You must specify class=ClassName'
|
4
|
+
task :url => :environment do
|
5
|
+
if clazz = ENV['class']
|
6
|
+
puts clazz.constantize.state_machine_definition.google_chart_url
|
7
|
+
else
|
8
|
+
puts "Missing argument: class. Please specify class=ClassName"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
desc 'Opens the google chart in your browser. You must specify class=ClassNAME'
|
13
|
+
task :open => :environment do
|
14
|
+
if clazz = ENV['class']
|
15
|
+
`open '#{::CGI.unescape(clazz.constantize.state_machine_definition.google_chart_url)}'`
|
16
|
+
else
|
17
|
+
puts "Missing argument: class. Please specify class=ClassName"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/spec/active_record_spec.rb
CHANGED
@@ -111,6 +111,24 @@ describe ActiveRecord do
|
|
111
111
|
user.errors.entries.should == [['activation_code', 'is invalid']]
|
112
112
|
end
|
113
113
|
|
114
|
+
it "rollsback if an exception is raised" do
|
115
|
+
user_class = Class.new(User)
|
116
|
+
user_class.instance_eval do
|
117
|
+
define_method :without_managed_state_invite do
|
118
|
+
User.create!(:name => 'name2') #this shouldn't be persisted
|
119
|
+
User.create! #this should raise an error
|
120
|
+
end
|
121
|
+
end
|
122
|
+
user_class.count.should == 0
|
123
|
+
user = user_class.create!(:name => 'name')
|
124
|
+
expect {
|
125
|
+
user.transaction { user.invite_and_save }
|
126
|
+
}.to raise_error(ActiveRecord::RecordInvalid,
|
127
|
+
"Validation failed: Name can't be blank")
|
128
|
+
user_class.count.should == 1
|
129
|
+
user_class.first.name.should == 'name'
|
130
|
+
user_class.first.should be_new
|
131
|
+
end
|
114
132
|
end
|
115
133
|
|
116
134
|
describe "event_and_save!" do
|
@@ -161,6 +179,25 @@ describe ActiveRecord do
|
|
161
179
|
user.should be_invited
|
162
180
|
end
|
163
181
|
|
182
|
+
it "rollsback if an exception is raised" do
|
183
|
+
user_class = Class.new(User)
|
184
|
+
user_class.instance_eval do
|
185
|
+
define_method :without_managed_state_invite do
|
186
|
+
User.create!(:name => 'name2') #this shouldn't be persisted
|
187
|
+
User.create! #this should raise an error
|
188
|
+
end
|
189
|
+
end
|
190
|
+
user_class.count.should == 0
|
191
|
+
user = user_class.create!(:name => 'name')
|
192
|
+
expect {
|
193
|
+
user.transaction { user.invite_and_save! }
|
194
|
+
}.to raise_error(ActiveRecord::RecordInvalid,
|
195
|
+
"Validation failed: Name can't be blank")
|
196
|
+
user_class.count.should == 1
|
197
|
+
user_class.first.name.should == 'name'
|
198
|
+
user_class.first.should be_new
|
199
|
+
end
|
200
|
+
|
164
201
|
end
|
165
202
|
|
166
203
|
describe "event" do
|
data/spec/mountable_spec.rb
CHANGED
@@ -3,14 +3,19 @@ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
|
3
3
|
describe "Mountable" do
|
4
4
|
before do
|
5
5
|
mountable_class = Class.new(SimpleStateMachine::StateMachineDefinition) do
|
6
|
-
|
7
|
-
|
8
|
-
|
6
|
+
event(:event, :state1 => :state2)
|
7
|
+
|
8
|
+
def decorator_class
|
9
|
+
SimpleStateMachine::Decorator
|
9
10
|
end
|
10
11
|
end
|
11
12
|
klass = Class.new do
|
13
|
+
attr_accessor :event_called
|
12
14
|
extend SimpleStateMachine::Mountable
|
13
|
-
|
15
|
+
mount_state_machine mountable_class
|
16
|
+
def without_managed_state_event
|
17
|
+
@event_called = true
|
18
|
+
end
|
14
19
|
end
|
15
20
|
@instance = klass.new
|
16
21
|
@instance.state = 'state1'
|
@@ -21,4 +26,9 @@ describe "Mountable" do
|
|
21
26
|
@instance.should_not be_state2
|
22
27
|
end
|
23
28
|
|
29
|
+
it "calls existing methods" do
|
30
|
+
@instance.event
|
31
|
+
@instance.should be_state2
|
32
|
+
@instance.event_called.should == true
|
33
|
+
end
|
24
34
|
end
|
@@ -2,7 +2,7 @@ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
|
2
2
|
require 'cgi'
|
3
3
|
|
4
4
|
describe SimpleStateMachine do
|
5
|
-
|
5
|
+
|
6
6
|
it "has an error that extends RuntimeError" do
|
7
7
|
SimpleStateMachine::IllegalStateTransitionError.superclass.should == RuntimeError
|
8
8
|
end
|
@@ -18,109 +18,138 @@ describe SimpleStateMachine do
|
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
|
-
it "
|
21
|
+
it "returns what the decorated method returns" do
|
22
22
|
klass = Class.new(@klass)
|
23
23
|
klass.instance_eval do
|
24
|
-
event :
|
24
|
+
event :event1, :state1 => :state2
|
25
|
+
define_method :event2 do
|
26
|
+
'event2'
|
27
|
+
end
|
28
|
+
event :event2, :state2 => :state3
|
25
29
|
end
|
26
30
|
example = klass.new
|
27
|
-
example.should
|
28
|
-
example.
|
29
|
-
example.should be_state2
|
30
|
-
example.event
|
31
|
-
example.should be_state3
|
31
|
+
example.event1.should == nil
|
32
|
+
example.event2.should == 'event2'
|
32
33
|
end
|
33
34
|
|
34
|
-
it "
|
35
|
+
it "calls existing methods" do
|
35
36
|
klass = Class.new(@klass)
|
36
37
|
klass.instance_eval do
|
37
|
-
|
38
|
+
attr_accessor :event_called
|
39
|
+
define_method :event do
|
40
|
+
@event_called = true
|
41
|
+
end
|
42
|
+
event :event, :state1 => :state2
|
38
43
|
end
|
39
44
|
example = klass.new
|
40
45
|
example.event
|
41
|
-
example.should
|
42
|
-
example = klass.new 'state2'
|
43
|
-
example.should be_state2
|
44
|
-
example.event
|
45
|
-
example.should be_state3
|
46
|
+
example.event_called.should == true
|
46
47
|
end
|
47
48
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
49
|
+
context "given an event has multiple transitions" do
|
50
|
+
it "changes state for all transitions" do
|
51
|
+
klass = Class.new(@klass)
|
52
|
+
klass.instance_eval do
|
53
|
+
event :event, :state1 => :state2, :state2 => :state3
|
54
|
+
end
|
55
|
+
example = klass.new
|
56
|
+
example.should be_state1
|
57
|
+
example.event
|
58
|
+
example.should be_state2
|
59
|
+
example.event
|
60
|
+
example.should be_state3
|
53
61
|
end
|
54
|
-
example = klass.new
|
55
|
-
example.event
|
56
|
-
example.should be_state3
|
57
|
-
example = klass.new 'state2'
|
58
|
-
example.should be_state2
|
59
|
-
example.event
|
60
|
-
example.should be_state3
|
61
62
|
end
|
62
63
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
64
|
+
context "given an event has multiple from states" do
|
65
|
+
it "changes state for all from states" do
|
66
|
+
klass = Class.new(@klass)
|
67
|
+
klass.instance_eval do
|
68
|
+
event :event, [:state1, :state2] => :state3
|
69
|
+
end
|
70
|
+
example = klass.new
|
71
|
+
example.event
|
72
|
+
example.should be_state3
|
73
|
+
example = klass.new 'state2'
|
74
|
+
example.should be_state2
|
75
|
+
example.event
|
76
|
+
example.should be_state3
|
77
|
+
end
|
72
78
|
end
|
73
79
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
80
|
+
context "given an event has :all as from state" do
|
81
|
+
it "changes state from all states" do
|
82
|
+
klass = Class.new(@klass)
|
83
|
+
klass.instance_eval do
|
84
|
+
event :other_event, :state1 => :state2
|
85
|
+
event :event, :all => :state3
|
79
86
|
end
|
80
|
-
|
87
|
+
example = klass.new
|
88
|
+
example.event
|
89
|
+
example.should be_state3
|
90
|
+
example = klass.new 'state2'
|
91
|
+
example.should be_state2
|
92
|
+
example.event
|
93
|
+
example.should be_state3
|
81
94
|
end
|
82
|
-
example = class_with_error.new
|
83
|
-
example.should be_state1
|
84
|
-
example.raise_error
|
85
|
-
example.should be_failed
|
86
95
|
end
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
+
|
97
|
+
context "given state is a symbol instead of a string" do
|
98
|
+
it "changes state" do
|
99
|
+
klass = Class.new(@klass)
|
100
|
+
klass.instance_eval do
|
101
|
+
event :event, :state1 => :state2
|
102
|
+
end
|
103
|
+
example = klass.new :state1
|
104
|
+
example.state.should == :state1
|
105
|
+
example.send(:event)
|
106
|
+
example.should be_state2
|
107
|
+
end
|
96
108
|
end
|
97
109
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
110
|
+
context "given an RuntimeError begin state" do
|
111
|
+
it "changes state to error_state when error can be caught" do
|
112
|
+
class_with_error = Class.new(@klass)
|
113
|
+
class_with_error.instance_eval do
|
114
|
+
define_method :raise_error do
|
115
|
+
raise RuntimeError.new
|
116
|
+
end
|
117
|
+
event :raise_error, :state1 => :state2, RuntimeError => :failed
|
104
118
|
end
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
119
|
+
example = class_with_error.new
|
120
|
+
example.should be_state1
|
121
|
+
example.raise_error
|
122
|
+
example.should be_failed
|
123
|
+
end
|
124
|
+
|
125
|
+
it "changes state to error_state when error superclass can be caught" do
|
126
|
+
error_subclass = Class.new(RuntimeError)
|
127
|
+
class_with_error = Class.new(@klass)
|
128
|
+
class_with_error.instance_eval do
|
129
|
+
define_method :raise_error do
|
130
|
+
raise error_subclass.new
|
131
|
+
end
|
132
|
+
event :raise_error, :state1 => :state2, RuntimeError => :failed
|
133
|
+
end
|
134
|
+
example = class_with_error.new
|
135
|
+
example.should be_state1
|
136
|
+
example.raise_error
|
137
|
+
example.should be_failed
|
138
|
+
end
|
110
139
|
end
|
111
140
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
141
|
+
context "given an invalid state_transition is called" do
|
142
|
+
it "raises an IllegalStateTransitionError" do
|
143
|
+
klass = Class.new(@klass)
|
144
|
+
klass.instance_eval do
|
145
|
+
event :event, :state1 => :state2
|
146
|
+
event :event2, :state2 => :state3
|
118
147
|
end
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
148
|
+
example = klass.new
|
149
|
+
lambda { example.event2 }.should raise_error(
|
150
|
+
SimpleStateMachine::IllegalStateTransitionError,
|
151
|
+
"You cannot 'event2' when state is 'state1'")
|
152
|
+
end
|
124
153
|
end
|
125
154
|
|
126
155
|
end
|
@@ -8,7 +8,7 @@ describe SimpleStateMachine::StateMachineDefinition do
|
|
8
8
|
def initialize(state = 'state1')
|
9
9
|
@state = state
|
10
10
|
end
|
11
|
-
event :event1, :state1 => :state2, :state2 => :state3
|
11
|
+
event :event1, :state1 => :state2, :state2 => :state3
|
12
12
|
end
|
13
13
|
@smd = @klass.state_machine_definition
|
14
14
|
end
|
@@ -53,13 +53,103 @@ describe SimpleStateMachine::StateMachineDefinition do
|
|
53
53
|
subject.event2
|
54
54
|
subject.should be_state3
|
55
55
|
end
|
56
|
-
|
56
|
+
|
57
57
|
it "raise an error if an invalid state_transition is called" do
|
58
58
|
lambda { subject.event2 }.should raise_error(SimpleStateMachine::IllegalStateTransitionError, "You cannot 'event2' when state is 'state1'")
|
59
59
|
end
|
60
60
|
|
61
61
|
end
|
62
62
|
|
63
|
+
describe '#default_error_state' do
|
64
|
+
subject do
|
65
|
+
klass = Class.new do
|
66
|
+
attr_reader :state
|
67
|
+
extend SimpleStateMachine
|
68
|
+
state_machine_definition.default_error_state = :failed
|
69
|
+
|
70
|
+
def initialize(state = 'state1')
|
71
|
+
@state = state
|
72
|
+
end
|
73
|
+
|
74
|
+
def event1
|
75
|
+
raise "Some error during event"
|
76
|
+
end
|
77
|
+
event :event1, :state1 => :state2
|
78
|
+
end
|
79
|
+
klass.new
|
80
|
+
end
|
81
|
+
|
82
|
+
it "is set when an error occurs during an event" do
|
83
|
+
subject.state.should == 'state1'
|
84
|
+
subject.event1
|
85
|
+
subject.state.should == 'failed'
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
describe "#begin_states" do
|
90
|
+
before do
|
91
|
+
@klass = Class.new(SimpleStateMachine::StateMachineDefinition) do
|
92
|
+
def add_events
|
93
|
+
define_event(:event_a, :state1 => :state2)
|
94
|
+
define_event(:event_b, :state2 => :state3)
|
95
|
+
define_event(:event_c, :state1 => :state3)
|
96
|
+
define_event(:event_c, RuntimeError => :state3)
|
97
|
+
end
|
98
|
+
|
99
|
+
def decorator_class
|
100
|
+
SimpleStateMachine::Decorator
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
it "returns all 'from' states that aren't 'to' states" do
|
106
|
+
@klass.new.begin_states.should == [:state1, RuntimeError]
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
describe "#end_states" do
|
111
|
+
before do
|
112
|
+
@klass = Class.new(SimpleStateMachine::StateMachineDefinition) do
|
113
|
+
def add_events
|
114
|
+
define_event(:event_a, :state1 => :state2)
|
115
|
+
define_event(:event_b, :state2 => :state3)
|
116
|
+
define_event(:event_c, :state1 => :state3)
|
117
|
+
end
|
118
|
+
|
119
|
+
def decorator_class
|
120
|
+
SimpleStateMachine::Decorator
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
it "returns all 'to' states that aren't 'from' states" do
|
127
|
+
@klass.new.end_states.should == [:state3]
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
describe "#states" do
|
132
|
+
before do
|
133
|
+
@klass = Class.new(SimpleStateMachine::StateMachineDefinition) do
|
134
|
+
def add_events
|
135
|
+
define_event(:event_a, :state1 => :state2)
|
136
|
+
define_event(:event_b, :state2 => :state3)
|
137
|
+
define_event(:event_c, :state1 => :state3)
|
138
|
+
end
|
139
|
+
|
140
|
+
def decorator_class
|
141
|
+
SimpleStateMachine::Decorator
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
it "returns all states" do
|
148
|
+
@klass.new.states.map(&:to_s).sort.should == %w{state1 state2 state3}
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
|
63
153
|
describe "#transitions" do
|
64
154
|
it "has a list of transitions" do
|
65
155
|
@smd.transitions.should be_a(Array)
|
@@ -73,16 +163,15 @@ describe SimpleStateMachine::StateMachineDefinition do
|
|
73
163
|
end
|
74
164
|
end
|
75
165
|
|
76
|
-
describe "#
|
77
|
-
it "converts to
|
78
|
-
@smd.
|
166
|
+
describe "#to_graphviz_dot" do
|
167
|
+
it "converts to graphviz dot format" do
|
168
|
+
@smd.to_graphviz_dot.should == %("state1"->"state2"[label=event1];"state2"->"state3"[label=event1])
|
79
169
|
end
|
80
170
|
end
|
81
171
|
|
82
172
|
describe "#google_chart_url" do
|
83
173
|
it "shows the state and event dependencies as a Google chart" do
|
84
|
-
|
85
|
-
@smd.google_chart_url.should == "http://chart.googleapis.com/chart?cht=gv&chl=digraph{#{::CGI.escape @smd.to_graphiz_dot}}"
|
174
|
+
@smd.google_chart_url.should == "http://chart.googleapis.com/chart?cht=gv&chl=digraph{#{::CGI.escape @smd.to_graphviz_dot}}"
|
86
175
|
end
|
87
176
|
end
|
88
177
|
end
|
data/spec/state_machine_spec.rb
CHANGED
@@ -22,5 +22,39 @@ describe SimpleStateMachine::StateMachine do
|
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
|
+
describe "#raised_error" do
|
26
|
+
context "given an error can be caught" do
|
27
|
+
let(:class_with_error) do
|
28
|
+
Class.new do
|
29
|
+
extend SimpleStateMachine
|
30
|
+
def initialize(state = 'state1'); @state = state; end
|
31
|
+
def raise_error
|
32
|
+
raise "Something went wrong"
|
33
|
+
end
|
34
|
+
event :raise_error, :state1 => :state2,
|
35
|
+
RuntimeError => :failed
|
36
|
+
event :retry, :failed => :success
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
it "the raised error is accessible" do
|
41
|
+
example = class_with_error.new
|
42
|
+
example.raise_error
|
43
|
+
raised_error = example.state_machine.raised_error
|
44
|
+
raised_error.should be_a(RuntimeError)
|
45
|
+
raised_error.message.should == "Something went wrong"
|
46
|
+
end
|
47
|
+
|
48
|
+
it "the raised error is set to nil on the next transition" do
|
49
|
+
example = class_with_error.new
|
50
|
+
example.raise_error
|
51
|
+
example.state_machine.raised_error.should be
|
52
|
+
example.retry
|
53
|
+
example.state_machine.raised_error.should_not be
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
25
59
|
end
|
26
60
|
|
metadata
CHANGED
@@ -1,13 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: simple_state_machine
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
5
|
-
prerelease:
|
4
|
+
hash: 961916020
|
5
|
+
prerelease: 6
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
-
|
10
|
-
|
8
|
+
- 6
|
9
|
+
- 0
|
10
|
+
- pre
|
11
|
+
version: 0.6.0.pre
|
11
12
|
platform: ruby
|
12
13
|
authors:
|
13
14
|
- Marek de Heus
|
@@ -16,7 +17,7 @@ autorequire:
|
|
16
17
|
bindir: bin
|
17
18
|
cert_chain: []
|
18
19
|
|
19
|
-
date: 2011-
|
20
|
+
date: 2011-07-06 00:00:00 +02:00
|
20
21
|
default_executable:
|
21
22
|
dependencies:
|
22
23
|
- !ruby/object:Gem::Dependency
|
@@ -91,6 +92,20 @@ dependencies:
|
|
91
92
|
version: "0"
|
92
93
|
type: :development
|
93
94
|
version_requirements: *id005
|
95
|
+
- !ruby/object:Gem::Dependency
|
96
|
+
name: ruby-debug
|
97
|
+
prerelease: false
|
98
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
99
|
+
none: false
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
hash: 3
|
104
|
+
segments:
|
105
|
+
- 0
|
106
|
+
version: "0"
|
107
|
+
type: :development
|
108
|
+
version_requirements: *id006
|
94
109
|
description: Simple State Machine is a state machine that focuses on events instead of states
|
95
110
|
email:
|
96
111
|
- FIX@example.com
|
@@ -120,8 +135,7 @@ files:
|
|
120
135
|
- lib/simple_state_machine/railtie.rb
|
121
136
|
- lib/simple_state_machine/simple_state_machine.rb
|
122
137
|
- lib/simple_state_machine/version.rb
|
123
|
-
- lib/tasks/
|
124
|
-
- rails/graphiz.rake
|
138
|
+
- lib/tasks/graphviz.rake
|
125
139
|
- simple_state_machine.gemspec
|
126
140
|
- spec/active_record_spec.rb
|
127
141
|
- spec/decorator_spec.rb
|
@@ -152,12 +166,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
152
166
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
153
167
|
none: false
|
154
168
|
requirements:
|
155
|
-
- - "
|
169
|
+
- - ">"
|
156
170
|
- !ruby/object:Gem::Version
|
157
|
-
hash:
|
171
|
+
hash: 25
|
158
172
|
segments:
|
159
|
-
-
|
160
|
-
|
173
|
+
- 1
|
174
|
+
- 3
|
175
|
+
- 1
|
176
|
+
version: 1.3.1
|
161
177
|
requirements: []
|
162
178
|
|
163
179
|
rubyforge_project:
|
data/lib/tasks/graphiz.rake
DELETED
@@ -1,13 +0,0 @@
|
|
1
|
-
namespace :ssm do
|
2
|
-
namespace :graph do
|
3
|
-
desc 'Generate a url for a google chart for [class]'
|
4
|
-
task :url => :environment do
|
5
|
-
puts ENV['class'].constantize.state_machine_definition.google_chart_url
|
6
|
-
end
|
7
|
-
|
8
|
-
desc 'Opens the google chart in the browser for [class]'
|
9
|
-
task :open => :environment do
|
10
|
-
`open '#{::CGI.unescape(ENV['class'].constantize.state_machine_definition.google_chart_url)}'`
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|