simple_state_machine 0.5.3 → 0.6.2
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.
- checksums.yaml +7 -0
- data/Changelog.rdoc +23 -0
- data/README.rdoc +154 -100
- data/lib/simple_state_machine/active_record.rb +2 -62
- data/lib/simple_state_machine/decorator/active_record.rb +68 -0
- data/lib/simple_state_machine/decorator/default.rb +91 -0
- data/lib/simple_state_machine/railtie.rb +1 -1
- data/lib/simple_state_machine/simple_state_machine.rb +8 -251
- data/lib/simple_state_machine/state_machine.rb +88 -0
- data/lib/simple_state_machine/state_machine_definition.rb +72 -0
- data/lib/simple_state_machine/tools/graphviz.rb +21 -0
- data/lib/simple_state_machine/tools/inspector.rb +44 -0
- data/lib/simple_state_machine/transition.rb +40 -0
- data/lib/simple_state_machine/version.rb +1 -1
- data/lib/simple_state_machine.rb +13 -3
- data/lib/tasks/graphviz.rake +31 -0
- metadata +37 -150
- data/.gitignore +0 -1
- data/.rspec +0 -3
- data/Gemfile +0 -4
- data/Rakefile +0 -28
- data/autotest/discover.rb +0 -1
- data/examples/conversation.rb +0 -33
- data/examples/lamp.rb +0 -21
- data/examples/relationship.rb +0 -87
- data/examples/traffic_light.rb +0 -17
- data/examples/user.rb +0 -37
- data/lib/tasks/graphiz.rake +0 -13
- data/rails/graphiz.rake +0 -16
- data/simple_state_machine.gemspec +0 -31
- data/spec/active_record_spec.rb +0 -223
- data/spec/decorator_spec.rb +0 -195
- data/spec/examples_spec.rb +0 -60
- data/spec/mountable_spec.rb +0 -24
- data/spec/simple_state_machine_spec.rb +0 -129
- data/spec/spec_helper.rb +0 -7
- data/spec/state_machine_definition_spec.rb +0 -89
- data/spec/state_machine_spec.rb +0 -26
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 03d0976fcf4c2dccd328f82cb00f8b5050f2cc052b578ae98d0227aca37afb5b
|
4
|
+
data.tar.gz: 193ae0211b9f82a386317032a2fe3ee245d263801cfab69981c3c6d9cfbe2053
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4e31fd218c64886a4e5fba9334d4b5b71c9bdcbc1fd3216df488a3edc25d6d9a846336887c700623c84b401fc89a6a4afdaa02a0a7ad0d7e3e9456c6bcfdba2e
|
7
|
+
data.tar.gz: 4659c64c0b4422967418ca9289ce97e259defd2c4bcbd621e51e37422b2d036b2c8bede71b8b59c97a81137aa076a11403d47c6de2231314272b611b0d7cc08a
|
data/Changelog.rdoc
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
== 0.6.1
|
2
|
+
- Run against latest rubies and activerecord
|
3
|
+
- Remove old rubygems version requirement
|
4
|
+
- Remove some trailing whitespace [Petrik]
|
5
|
+
- Use `expect` instead if old `should` notation [Petrik]
|
6
|
+
- Drop support for Ruby 1.9 [Petrik]
|
7
|
+
|
8
|
+
== 0.6.0
|
9
|
+
=== Enhancements
|
10
|
+
- Define decorated methods as #{event_name}_with_managed_state instead of with_managed_state_#{event_name} [Petrik]
|
11
|
+
- Added raised_error method to access caught Errors [Petrik]
|
12
|
+
- Added support for a default error state [Marek de Heus]
|
13
|
+
- Removed transaction support, you can still add it manually, see active_record_spec.rb [Marek de Heus]
|
14
|
+
- Allow catching errors for subclasses of the error [Petrik]
|
15
|
+
- Added gem install instructions [Marek de Heus]
|
16
|
+
- Added define_event method to simplify mountable state machine definitions [Petrik]
|
17
|
+
- Added end_states method so we can check the state machine correctness [Petrik]
|
18
|
+
- Added begin_states method so we can check the state machine correctness [Petrik]
|
19
|
+
|
20
|
+
=== Bugfixes
|
21
|
+
- Fixed bug in Inspector, from state can now be a Error class [Marek de Heus]
|
22
|
+
- Make sure from and end states are uniq [Petrik]
|
23
|
+
- fixed typos (graphiz -> graphviz) (redmar-master) [rjk]
|
data/README.rdoc
CHANGED
@@ -1,43 +1,29 @@
|
|
1
1
|
= SimpleStateMachine
|
2
2
|
|
3
|
+
{<img src="https://github.com/mdh/ssm/actions/workflows/build.yml/badge.svg" />}[https://github.com/mdh/ssm/actions?query=workflow%3A.github%2Fworkflows%2Fbuild.yml+branch%3Amaster++]
|
4
|
+
|
3
5
|
A simple DSL to decorate existing methods with state transition guards.
|
4
6
|
|
5
7
|
Instead of using a DSL to define events, SimpleStateMachine decorates methods
|
6
8
|
to help you encapsulate state and guard state transitions.
|
7
9
|
|
8
|
-
It supports exception rescuing, google chart visualization and mountable
|
9
|
-
|
10
|
-
== Basic example
|
11
|
-
|
12
|
-
class LampSwitch
|
13
|
-
|
14
|
-
extend SimpleStateMachine
|
10
|
+
It supports exception rescuing, google chart visualization and mountable state machines.
|
15
11
|
|
16
|
-
|
17
|
-
self.state = 'off'
|
18
|
-
end
|
12
|
+
== Usage
|
19
13
|
|
20
|
-
|
21
|
-
|
22
|
-
end
|
23
|
-
event :push_switch, :off => :on,
|
24
|
-
:on => :off
|
14
|
+
Define an event and specify how the state should transition. If we want the state to change
|
15
|
+
from *pending* to *active* we write:
|
25
16
|
|
26
|
-
|
27
|
-
|
28
|
-
lamp = LampSwitch.new
|
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
|
17
|
+
event :activate_account, :pending => :active
|
36
18
|
|
19
|
+
That's it. You can now call *activate_account* and the state will automatically change.
|
20
|
+
If the state change is not allowed, a SimpleStateMachine::IllegalStateTransitionError is
|
21
|
+
raised.
|
37
22
|
|
38
|
-
|
23
|
+
=== Methods with arguments
|
39
24
|
|
40
|
-
|
25
|
+
If you want to pass arguments and call other methods before the state transition, define your
|
26
|
+
event as a method.
|
41
27
|
|
42
28
|
def activate_account(activation_code)
|
43
29
|
# call other methods, no need to add these in callbacks
|
@@ -45,120 +31,187 @@ Define your event as a method, arguments are allowed:
|
|
45
31
|
end
|
46
32
|
|
47
33
|
Now mark the method as an event and specify how the state should transition
|
48
|
-
when the method is called.
|
34
|
+
when the method is called.
|
49
35
|
|
50
36
|
event :activate_account, :pending => :active
|
51
37
|
|
52
|
-
That's it!
|
53
|
-
You can now call activate_account and the state will automatically change.
|
54
|
-
If the state change is not allowed, a SimpleStateMachine::IllegalStateTransitionError is raised.
|
55
38
|
|
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
39
|
|
66
|
-
|
40
|
+
== Basic example
|
67
41
|
|
68
|
-
|
69
|
-
|
42
|
+
class LampSwitch
|
43
|
+
|
44
|
+
extend SimpleStateMachine
|
45
|
+
|
46
|
+
def initialize
|
47
|
+
self.state = 'off'
|
48
|
+
end
|
49
|
+
|
50
|
+
event :push_switch, :off => :on,
|
51
|
+
:on => :off
|
70
52
|
|
71
|
-
def download_data
|
72
|
-
Service.download_data
|
73
53
|
end
|
74
|
-
event :download_data, :pending => :downloaded,
|
75
|
-
Service::ConnectionError => :download_failed
|
76
54
|
|
77
|
-
|
78
|
-
state
|
55
|
+
lamp = LampSwitch.new
|
56
|
+
lamp.state # => 'off'
|
57
|
+
lamp.off? # => true
|
58
|
+
lamp.push_switch #
|
59
|
+
lamp.state # => 'on'
|
60
|
+
lamp.on? # => true
|
61
|
+
lamp.push_switch #
|
62
|
+
lamp.off? # => true
|
79
63
|
|
80
|
-
=== Catching all from states
|
81
|
-
If an event should transition from all states you can use :all
|
82
64
|
|
83
|
-
|
65
|
+
== ActiveRecord
|
84
66
|
|
85
|
-
|
67
|
+
For ActiveRecord methods are decorated with state transition guards _and_ persistence.
|
68
|
+
Methods marked as events behave like ActiveRecord *save* and *save!*.
|
86
69
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
70
|
+
=== Example
|
71
|
+
|
72
|
+
To add a state machine to an ActiveRecord class, you will have to:
|
73
|
+
* extend SimpleStateMachine::ActiveRecord,
|
74
|
+
* set the initial state in after_initialize,
|
75
|
+
* turn methods into events
|
91
76
|
|
92
77
|
class User < ActiveRecord::Base
|
93
|
-
|
78
|
+
|
94
79
|
extend SimpleStateMachine::ActiveRecord
|
95
80
|
|
96
|
-
|
97
|
-
self.
|
98
|
-
# if you get an ActiveRecord::MissingAttributeError
|
99
|
-
# you'll probably need to do (http://bit.ly/35q23b):
|
100
|
-
# write_attribute(:ssm_state, "pending") unless read_attribute(:ssm_state)
|
81
|
+
after_initialize do
|
82
|
+
self.state ||= 'pending'
|
101
83
|
end
|
102
|
-
|
84
|
+
|
103
85
|
def invite
|
104
86
|
self.activation_code = Digest::SHA1.hexdigest("salt #{Time.now.to_f}")
|
105
|
-
#send_activation_email
|
106
87
|
end
|
107
88
|
event :invite, :pending => :invited
|
108
|
-
|
109
|
-
def confirm_invitation activation_code
|
110
|
-
if self.activation_code != activation_code
|
111
|
-
errors.add 'activation_code', 'is invalid'
|
112
|
-
end
|
113
|
-
end
|
114
|
-
event :confirm_invitation, :invited => :active
|
115
89
|
end
|
116
90
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
91
|
+
user = User.new
|
92
|
+
user.pending? # => true
|
93
|
+
user.invite # => true
|
94
|
+
user.invited? # => true
|
95
|
+
user.activation_code # => 'SOMEDIGEST'
|
121
96
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
- active?
|
97
|
+
For the invite method this generates the following event methods
|
98
|
+
* *invite* (behaves like ActiveRecord save )
|
99
|
+
* *invite!* (behaves like ActiveRecord save!)
|
126
100
|
|
127
101
|
If you want to be more verbose you can also use:
|
128
|
-
|
129
|
-
|
102
|
+
* *invite_and_save* (alias for invite)
|
103
|
+
* *invite_and_save!* (alias for invite!)
|
130
104
|
|
131
|
-
== Mountable Example
|
132
105
|
|
133
|
-
|
106
|
+
=== Using ActiveRecord / ActiveModel validations
|
107
|
+
|
108
|
+
When using ActiveRecord / ActiveModel you can add an error to the errors object.
|
109
|
+
This will prevent the state from being changed.
|
110
|
+
|
111
|
+
If we add an activate_account method to User
|
134
112
|
|
135
|
-
class
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
113
|
+
class User < ActiveRecord::Base
|
114
|
+
...
|
115
|
+
def activate_account(activation_code)
|
116
|
+
if activation_code_invalid?(activation_code)
|
117
|
+
errors.add(:activation_code, 'Invalid')
|
118
|
+
end
|
140
119
|
end
|
120
|
+
event :activate_account, :invited => :confirmed
|
121
|
+
...
|
141
122
|
end
|
142
123
|
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
124
|
+
user.confirm_invitation!('INVALID') # => raises ActiveRecord::RecordInvalid,
|
125
|
+
# "Validation failed: Activation code is invalid"
|
126
|
+
user.confirmed? # => false
|
127
|
+
user.confirm_invitation!('VALID')
|
128
|
+
user.confirmed? # => true
|
147
129
|
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
130
|
+
== Mountable StateMachines
|
131
|
+
|
132
|
+
If you like to separate your state machine from your model class, you can do so as following:
|
133
|
+
|
134
|
+
class MyStateMachine < SimpleStateMachine::StateMachineDefinition
|
135
|
+
|
136
|
+
event :invite, :new => :invited
|
137
|
+
event :confirm_invitation, :invited => :active
|
138
|
+
|
139
|
+
def decorator_class
|
140
|
+
SimpleStateMachine::Decorator::Default
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
class User < ActiveRecord::Base
|
145
|
+
|
146
|
+
extend SimpleStateMachine::Mountable
|
147
|
+
mount_state_machine MyStateMachine
|
148
|
+
|
149
|
+
after_initialize do
|
150
|
+
self.state ||= 'new'
|
152
151
|
end
|
153
152
|
|
153
|
+
end
|
154
|
+
|
154
155
|
|
155
|
-
==
|
156
|
+
== Transitions
|
156
157
|
|
157
|
-
|
158
|
+
=== Catching all from states
|
159
|
+
If an event should transition from all other defined states, you can use the *:all* state:
|
160
|
+
|
161
|
+
event :suspend, :all => :suspended
|
162
|
+
|
163
|
+
|
164
|
+
=== Catching exceptions
|
165
|
+
|
166
|
+
You can let the state machine handle exceptions by specifying the failure state for an Error:
|
167
|
+
|
168
|
+
def download_data
|
169
|
+
raise Service::ConnectionError, "Uhoh"
|
170
|
+
end
|
171
|
+
event :download_data, Service::ConnectionError => :download_failed
|
158
172
|
|
173
|
+
download_data # catches Service::ConnectionError
|
174
|
+
state # => "download_failed"
|
175
|
+
state_machine.raised_error # "Uhoh"
|
176
|
+
|
177
|
+
|
178
|
+
=== Default error state
|
179
|
+
|
180
|
+
To automatically catch all exceptions to a default error state use default_error_state:
|
181
|
+
|
182
|
+
state_machine_definition.default_error_state = :failed
|
183
|
+
|
184
|
+
== Transactions
|
185
|
+
|
186
|
+
If you want to run events in transactions run them in a transaction block:
|
187
|
+
|
188
|
+
user.transaction { user.invite! }
|
189
|
+
|
190
|
+
== Tools
|
191
|
+
|
192
|
+
===Generating state diagrams
|
193
|
+
|
194
|
+
When using Rails/ActiveRecord you can generate a state diagram of the state machine via the
|
195
|
+
built in rake tasks.
|
196
|
+
For details run:
|
197
|
+
|
198
|
+
rake -T ssm
|
199
|
+
|
200
|
+
A Googlechart example:
|
201
|
+
http://tinyurl.com/79xztr6
|
202
|
+
|
203
|
+
== Installation
|
204
|
+
|
205
|
+
Use gem install:
|
206
|
+
|
207
|
+
gem install simple_state_machine
|
208
|
+
|
209
|
+
Or add it to your Gemfile:
|
210
|
+
|
211
|
+
gem 'simple_state_machine'
|
159
212
|
|
160
213
|
== Note on Patches/Pull Requests
|
161
|
-
|
214
|
+
|
162
215
|
* Fork the project.
|
163
216
|
* Make your feature addition or bug fix.
|
164
217
|
* Add tests for it. This is important so I don't break it in a
|
@@ -167,6 +220,7 @@ If your using rails you get rake tasks for generating a graphiz google chart of
|
|
167
220
|
(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
221
|
* Send me a pull request. Bonus points for topic branches.
|
169
222
|
|
223
|
+
|
170
224
|
== Copyright
|
171
225
|
|
172
226
|
Copyright (c) 2010 Marek & Petrik. See LICENSE for details.
|
@@ -4,71 +4,11 @@ module SimpleStateMachine::ActiveRecord
|
|
4
4
|
include SimpleStateMachine::Extendable
|
5
5
|
include SimpleStateMachine::Inheritable
|
6
6
|
|
7
|
-
class Decorator < SimpleStateMachine::Decorator
|
8
|
-
|
9
|
-
# decorates subject with:
|
10
|
-
# * {event_name}_and_save
|
11
|
-
# * {event_name}_and_save!
|
12
|
-
# * {event_name}!
|
13
|
-
# * {event_name}
|
14
|
-
def decorate transition
|
15
|
-
super transition
|
16
|
-
event_name = transition.event_name.to_s
|
17
|
-
event_name_and_save = "#{event_name}_and_save"
|
18
|
-
unless @subject.method_defined?(event_name_and_save)
|
19
|
-
@subject.send(:define_method, event_name_and_save) do |*args|
|
20
|
-
old_state = self.send(self.class.state_machine_definition.state_method)
|
21
|
-
send "with_managed_state_#{event_name}", *args
|
22
|
-
if !self.errors.entries.empty?
|
23
|
-
self.send("#{self.class.state_machine_definition.state_method}=", old_state)
|
24
|
-
return false
|
25
|
-
else
|
26
|
-
if save
|
27
|
-
return true
|
28
|
-
else
|
29
|
-
self.send("#{self.class.state_machine_definition.state_method}=", old_state)
|
30
|
-
return false
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
34
|
-
@subject.send :alias_method, "#{transition.event_name}", event_name_and_save
|
35
|
-
end
|
36
|
-
event_name_and_save_bang = "#{event_name_and_save}!"
|
37
|
-
unless @subject.method_defined?(event_name_and_save_bang)
|
38
|
-
@subject.send(:define_method, event_name_and_save_bang) do |*args|
|
39
|
-
old_state = self.send(self.class.state_machine_definition.state_method)
|
40
|
-
send "with_managed_state_#{event_name}", *args
|
41
|
-
if !self.errors.entries.empty?
|
42
|
-
self.send("#{self.class.state_machine_definition.state_method}=", old_state)
|
43
|
-
raise ActiveRecord::RecordInvalid.new(self)
|
44
|
-
end
|
45
|
-
begin
|
46
|
-
save!
|
47
|
-
rescue ActiveRecord::RecordInvalid
|
48
|
-
self.send("#{self.class.state_machine_definition.state_method}=", old_state)
|
49
|
-
raise #re raise
|
50
|
-
end
|
51
|
-
end
|
52
|
-
@subject.send :alias_method, "#{transition.event_name}!", event_name_and_save_bang
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
protected
|
57
|
-
|
58
|
-
def alias_event_methods event_name
|
59
|
-
@subject.send :alias_method, "without_managed_state_#{event_name}", event_name
|
60
|
-
end
|
61
|
-
|
62
|
-
def define_state_setter_method; end
|
63
|
-
|
64
|
-
def define_state_getter_method; end
|
65
|
-
|
66
|
-
end
|
67
|
-
|
68
7
|
def state_machine_definition
|
69
8
|
unless @state_machine_definition
|
70
9
|
@state_machine_definition = SimpleStateMachine::StateMachineDefinition.new
|
71
|
-
@state_machine_definition.
|
10
|
+
@state_machine_definition.decorator_class = SimpleStateMachine::Decorator::ActiveRecord
|
11
|
+
@state_machine_definition.subject = self
|
72
12
|
end
|
73
13
|
@state_machine_definition
|
74
14
|
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module SimpleStateMachine
|
2
|
+
module Decorator
|
3
|
+
class ActiveRecord < SimpleStateMachine::Decorator::Default
|
4
|
+
|
5
|
+
# decorates subject with:
|
6
|
+
# * {event_name}_and_save
|
7
|
+
# * {event_name}_and_save!
|
8
|
+
# * {event_name}!
|
9
|
+
# * {event_name}
|
10
|
+
def decorate transition
|
11
|
+
super transition
|
12
|
+
event_name = transition.event_name.to_s
|
13
|
+
decorate_save event_name
|
14
|
+
decorate_save! event_name
|
15
|
+
end
|
16
|
+
|
17
|
+
protected
|
18
|
+
|
19
|
+
def decorate_save event_name
|
20
|
+
event_name_and_save = "#{event_name}_and_save"
|
21
|
+
unless @subject.method_defined?(event_name_and_save)
|
22
|
+
@subject.send(:define_method, event_name_and_save) do |*args|
|
23
|
+
old_state = self.send(self.class.state_machine_definition.state_method)
|
24
|
+
send "#{event_name}_with_managed_state", *args
|
25
|
+
if self.errors.entries.empty? && save
|
26
|
+
true
|
27
|
+
else
|
28
|
+
self.send("#{self.class.state_machine_definition.state_method}=", old_state)
|
29
|
+
false
|
30
|
+
end
|
31
|
+
end
|
32
|
+
@subject.send :alias_method, "#{event_name}", event_name_and_save
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def decorate_save! event_name
|
37
|
+
event_name_and_save_bang = "#{event_name}_and_save!"
|
38
|
+
unless @subject.method_defined?(event_name_and_save_bang)
|
39
|
+
@subject.send(:define_method, event_name_and_save_bang) do |*args|
|
40
|
+
old_state = self.send(self.class.state_machine_definition.state_method)
|
41
|
+
send "#{event_name}_with_managed_state", *args
|
42
|
+
if self.errors.entries.empty?
|
43
|
+
begin
|
44
|
+
save!
|
45
|
+
rescue ::ActiveRecord::RecordInvalid
|
46
|
+
self.send("#{self.class.state_machine_definition.state_method}=", old_state)
|
47
|
+
raise #re raise
|
48
|
+
end
|
49
|
+
else
|
50
|
+
self.send("#{self.class.state_machine_definition.state_method}=", old_state)
|
51
|
+
raise ::ActiveRecord::RecordInvalid.new(self)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
@subject.send :alias_method, "#{event_name}!", event_name_and_save_bang
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def alias_event_methods event_name
|
59
|
+
@subject.send :alias_method, "#{event_name}_without_managed_state", event_name
|
60
|
+
end
|
61
|
+
|
62
|
+
def define_state_setter_method; end
|
63
|
+
|
64
|
+
def define_state_getter_method; end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
module SimpleStateMachine
|
2
|
+
module Decorator
|
3
|
+
##
|
4
|
+
# Decorates @subject with methods to access the state machine
|
5
|
+
class Default
|
6
|
+
|
7
|
+
attr_writer :subject
|
8
|
+
def initialize(subject)
|
9
|
+
@subject = subject
|
10
|
+
end
|
11
|
+
|
12
|
+
def decorate transition
|
13
|
+
define_state_machine_method
|
14
|
+
define_state_getter_method
|
15
|
+
define_state_setter_method
|
16
|
+
|
17
|
+
define_state_helper_method(transition.from)
|
18
|
+
define_state_helper_method(transition.to)
|
19
|
+
define_event_method(transition.event_name)
|
20
|
+
decorate_event_method(transition.event_name)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def define_state_machine_method
|
26
|
+
unless any_method_defined?("state_machine")
|
27
|
+
@subject.send(:define_method, "state_machine") do
|
28
|
+
@state_machine ||= StateMachine.new(self)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def define_state_helper_method state
|
34
|
+
unless any_method_defined?("#{state.to_s}?")
|
35
|
+
@subject.send(:define_method, "#{state.to_s}?") do
|
36
|
+
self.send(self.class.state_machine_definition.state_method) == state.to_s
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def define_event_method event_name
|
42
|
+
unless any_method_defined?("#{event_name}")
|
43
|
+
@subject.send(:define_method, "#{event_name}") {}
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def decorate_event_method event_name
|
48
|
+
# TODO put in transaction for activeRecord?
|
49
|
+
unless @subject.method_defined?("#{event_name}_with_managed_state")
|
50
|
+
@subject.send(:define_method, "#{event_name}_with_managed_state") do |*args|
|
51
|
+
return state_machine.transition(event_name) do
|
52
|
+
send("#{event_name}_without_managed_state", *args)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
alias_event_methods event_name
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def define_state_setter_method
|
60
|
+
unless any_method_defined?("#{state_method}=")
|
61
|
+
@subject.send(:define_method, "#{state_method}=") do |new_state|
|
62
|
+
instance_variable_set(:"@#{self.class.state_machine_definition.state_method}", new_state)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def define_state_getter_method
|
68
|
+
unless any_method_defined?(state_method)
|
69
|
+
@subject.send(:attr_reader, state_method)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def any_method_defined?(method)
|
74
|
+
@subject.method_defined?(method) ||
|
75
|
+
@subject.protected_method_defined?(method) ||
|
76
|
+
@subject.private_method_defined?(method)
|
77
|
+
end
|
78
|
+
|
79
|
+
protected
|
80
|
+
|
81
|
+
def alias_event_methods event_name
|
82
|
+
@subject.send :alias_method, "#{event_name}_without_managed_state", event_name
|
83
|
+
@subject.send :alias_method, event_name, "#{event_name}_with_managed_state"
|
84
|
+
end
|
85
|
+
|
86
|
+
def state_method
|
87
|
+
@subject.state_machine_definition.state_method
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|