simple_state_machine 0.5.3 → 0.6.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|