simple_state_machine 0.5.3 → 0.6.0.pre
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/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
|