state_machine 0.6.3 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +31 -1
- data/README.rdoc +33 -21
- data/Rakefile +2 -2
- data/examples/merb-rest/controller.rb +51 -0
- data/examples/merb-rest/model.rb +28 -0
- data/examples/merb-rest/view_edit.html.erb +24 -0
- data/examples/merb-rest/view_index.html.erb +23 -0
- data/examples/merb-rest/view_new.html.erb +13 -0
- data/examples/merb-rest/view_show.html.erb +17 -0
- data/examples/rails-rest/controller.rb +43 -0
- data/examples/rails-rest/migration.rb +11 -0
- data/examples/rails-rest/model.rb +23 -0
- data/examples/rails-rest/view_edit.html.erb +25 -0
- data/examples/rails-rest/view_index.html.erb +23 -0
- data/examples/rails-rest/view_new.html.erb +14 -0
- data/examples/rails-rest/view_show.html.erb +17 -0
- data/lib/state_machine/assertions.rb +2 -2
- data/lib/state_machine/callback.rb +14 -8
- data/lib/state_machine/condition_proxy.rb +3 -3
- data/lib/state_machine/event.rb +19 -21
- data/lib/state_machine/event_collection.rb +114 -0
- data/lib/state_machine/extensions.rb +127 -11
- data/lib/state_machine/guard.rb +1 -1
- data/lib/state_machine/integrations/active_record/locale.rb +2 -1
- data/lib/state_machine/integrations/active_record.rb +117 -39
- data/lib/state_machine/integrations/data_mapper/observer.rb +20 -64
- data/lib/state_machine/integrations/data_mapper.rb +71 -26
- data/lib/state_machine/integrations/sequel.rb +69 -21
- data/lib/state_machine/machine.rb +267 -139
- data/lib/state_machine/machine_collection.rb +145 -0
- data/lib/state_machine/matcher.rb +2 -2
- data/lib/state_machine/node_collection.rb +9 -4
- data/lib/state_machine/state.rb +22 -32
- data/lib/state_machine/state_collection.rb +66 -17
- data/lib/state_machine/transition.rb +259 -28
- data/lib/state_machine.rb +121 -56
- data/tasks/state_machine.rake +1 -0
- data/tasks/state_machine.rb +26 -0
- data/test/active_record.log +116877 -0
- data/test/functional/state_machine_test.rb +118 -12
- data/test/sequel.log +28542 -0
- data/test/unit/callback_test.rb +46 -1
- data/test/unit/condition_proxy_test.rb +55 -28
- data/test/unit/event_collection_test.rb +228 -0
- data/test/unit/event_test.rb +51 -46
- data/test/unit/integrations/active_record_test.rb +128 -70
- data/test/unit/integrations/data_mapper_test.rb +150 -58
- data/test/unit/integrations/sequel_test.rb +63 -6
- data/test/unit/invalid_event_test.rb +7 -0
- data/test/unit/machine_collection_test.rb +678 -0
- data/test/unit/machine_test.rb +198 -91
- data/test/unit/node_collection_test.rb +33 -30
- data/test/unit/state_collection_test.rb +112 -5
- data/test/unit/state_test.rb +23 -3
- data/test/unit/transition_test.rb +750 -89
- metadata +28 -3
data/CHANGELOG.rdoc
CHANGED
@@ -1,6 +1,36 @@
|
|
1
1
|
== master
|
2
2
|
|
3
|
-
== 0.
|
3
|
+
== 0.7.0 / 2009-04-03
|
4
|
+
|
5
|
+
* Add #{attribute}_event for automatically firing events when the object's action is called
|
6
|
+
* Make it easier to override state-driven behaviors
|
7
|
+
* Rollback state changes when the action fails during transitions
|
8
|
+
* Use :messages instead of :invalid_message for customizing validation errors
|
9
|
+
* Use more human-readable validation errors
|
10
|
+
* Add support for more ActiveRecord observer hooks
|
11
|
+
* Add support for targeting multiple specific state machines in DataMapper observer hooks
|
12
|
+
* Don't pass the result of the action as an argument to callbacks (access via Transition#result)
|
13
|
+
* Fix incorrect results being used when running transitions in parallel
|
14
|
+
* Fix transition args not being set when run in parallel
|
15
|
+
* Allow callback terminators to be set on an application-wide basis
|
16
|
+
* Only catch :halt during before / after transition callbacks
|
17
|
+
* Fix ActiveRecord predicates being overwritten if they're already defined in the class
|
18
|
+
* Allow machine options to be set on an integration-wide basis
|
19
|
+
* Turn transactions off by default in DataMapper integrations
|
20
|
+
* Add support for configuring the use of transactions
|
21
|
+
* Simplify reading/writing of attributes
|
22
|
+
* Simplify access to state machines via #state_machine(:attribute) without generating dupes
|
23
|
+
* Fix assumptions that dm-validations is always available in DataMapper integration
|
24
|
+
* Automatically define DataMapper properties for machine attributes if they don't exist
|
25
|
+
* Add Transition#qualified_event, #qualified_from_name, and #qualified_to_name
|
26
|
+
* Add #fire_events / #fire_events! for running events on multiple state machines in parallel
|
27
|
+
* Rename next_#{event}_transition to #{event}_transition
|
28
|
+
* Add #{attribute}_transitions for getting the list of transitions that can be run on an object
|
29
|
+
* Add #{attribute}_events for getting the list of events that can be fired on an object
|
30
|
+
* Use generated non-bang event when running bang version so that overriding one affects the other
|
31
|
+
* Provide access to arguments passed into an event from transition callbacks via Transition#args
|
32
|
+
|
33
|
+
== 0.6.3 / 2009-03-10
|
4
34
|
|
5
35
|
* Add support for customizing the graph's orientation
|
6
36
|
* Use the standard visualizations for initial (open arrow) and final (double circle) states
|
data/README.rdoc
CHANGED
@@ -45,6 +45,8 @@ Some brief, high-level features include:
|
|
45
45
|
* State-driven instance / class behavior
|
46
46
|
* State values of any data type
|
47
47
|
* Dynamically-generated state values
|
48
|
+
* Event parallelization
|
49
|
+
* Attribute-based event transitions
|
48
50
|
* Inheritance
|
49
51
|
* Internationalization
|
50
52
|
* GraphViz visualization creator
|
@@ -63,6 +65,7 @@ Below is an example of many of the features offered by this plugin, including:
|
|
63
65
|
* Conditional transitions
|
64
66
|
* State-driven instance behavior
|
65
67
|
* Customized state values
|
68
|
+
* Parallel events
|
66
69
|
|
67
70
|
Class definition:
|
68
71
|
|
@@ -125,17 +128,17 @@ Class definition:
|
|
125
128
|
end
|
126
129
|
end
|
127
130
|
|
128
|
-
state_machine :
|
129
|
-
event :
|
130
|
-
transition all => :
|
131
|
+
state_machine :alarm_state, :initial => :active, :namespace => 'alarm' do
|
132
|
+
event :enable do
|
133
|
+
transition all => :active
|
131
134
|
end
|
132
135
|
|
133
|
-
event :
|
134
|
-
transition all => :
|
136
|
+
event :disable do
|
137
|
+
transition all => :off
|
135
138
|
end
|
136
139
|
|
137
|
-
state :
|
138
|
-
state :
|
140
|
+
state :active, :value => 1
|
141
|
+
state :off, :value => 0
|
139
142
|
end
|
140
143
|
|
141
144
|
def initialize
|
@@ -168,7 +171,9 @@ like so:
|
|
168
171
|
vehicle.state_name # => :parked
|
169
172
|
vehicle.parked? # => true
|
170
173
|
vehicle.can_ignite? # => true
|
171
|
-
vehicle.
|
174
|
+
vehicle.ignite_transition # => #<StateMachine::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>
|
175
|
+
vehicle.state_events # => [:ignite]
|
176
|
+
vehicle.state_transitions # => [#<StateMachine::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>]
|
172
177
|
vehicle.speed # => 0
|
173
178
|
|
174
179
|
vehicle.ignite # => true
|
@@ -189,21 +194,28 @@ like so:
|
|
189
194
|
vehicle.park! # => StateMachine::InvalidTransition: Cannot transition state via :park from :second_gear
|
190
195
|
|
191
196
|
# Generic state predicates can raise exceptions if the value does not exist
|
192
|
-
vehicle.state?(:parked) # =>
|
193
|
-
vehicle.state?(:invalid) # =>
|
197
|
+
vehicle.state?(:parked) # => false
|
198
|
+
vehicle.state?(:invalid) # => IndexError: :invalid is an invalid name
|
194
199
|
|
195
200
|
# Namespaced machines have uniquely-generated methods
|
196
|
-
vehicle.
|
197
|
-
vehicle.
|
201
|
+
vehicle.alarm_state # => 1
|
202
|
+
vehicle.alarm_state_name # => :active
|
198
203
|
|
199
|
-
vehicle.
|
200
|
-
vehicle.
|
201
|
-
vehicle.
|
202
|
-
vehicle.
|
203
|
-
vehicle.
|
204
|
+
vehicle.can_disable_alarm? # => true
|
205
|
+
vehicle.disable_alarm # => true
|
206
|
+
vehicle.alarm_state # => 0
|
207
|
+
vehicle.alarm_state_name # => :off
|
208
|
+
vehicle.can_enable_alarm? # => true
|
204
209
|
|
205
|
-
vehicle.
|
206
|
-
vehicle.
|
210
|
+
vehicle.alarm_off? # => true
|
211
|
+
vehicle.alarm_active? # => false
|
212
|
+
|
213
|
+
# Events can be fired in parallel
|
214
|
+
vehicle.fire_events(:shift_down, :enable_alarm) # => true
|
215
|
+
vehicle.state_name # => :first_gear
|
216
|
+
vehicle.alarm_state_name # => :active
|
217
|
+
|
218
|
+
vehicle.fire_events!(:ignite, :enable_alarm) # => StateMachine::InvalidTransition: Cannot run events in parallel: ignite, enable_alarm
|
207
219
|
|
208
220
|
*Note* the comment made on the +initialize+ method in the class. In order for
|
209
221
|
state machine attributes to be properly initialized, <tt>super()</tt> must be called.
|
@@ -307,8 +319,8 @@ callbacks, validation errors, and observers. For example,
|
|
307
319
|
end
|
308
320
|
|
309
321
|
# Generic transition callback *before* the transition is performed
|
310
|
-
after_transition do |transition
|
311
|
-
Audit.log(self, transition)
|
322
|
+
after_transition do |transition|
|
323
|
+
Audit.log(self, transition) # self is the record
|
312
324
|
end
|
313
325
|
end
|
314
326
|
|
data/Rakefile
CHANGED
@@ -5,11 +5,11 @@ require 'rake/contrib/sshpublisher'
|
|
5
5
|
|
6
6
|
spec = Gem::Specification.new do |s|
|
7
7
|
s.name = 'state_machine'
|
8
|
-
s.version = '0.
|
8
|
+
s.version = '0.7.0'
|
9
9
|
s.platform = Gem::Platform::RUBY
|
10
10
|
s.summary = 'Adds support for creating state machines for attributes on any Ruby class'
|
11
11
|
|
12
|
-
s.files = FileList['{examples,lib,test}/**/*'] + %w(CHANGELOG.rdoc init.rb LICENSE Rakefile README.rdoc) - FileList['test/app_root/{log,log/*,script,script/*}']
|
12
|
+
s.files = FileList['{examples,lib,tasks,test}/**/*'] + %w(CHANGELOG.rdoc init.rb LICENSE Rakefile README.rdoc) - FileList['test/app_root/{log,log/*,script,script/*}']
|
13
13
|
s.require_path = 'lib'
|
14
14
|
s.has_rdoc = true
|
15
15
|
s.test_files = Dir['test/**/*_test.rb']
|
@@ -0,0 +1,51 @@
|
|
1
|
+
class Users < Application
|
2
|
+
# GET /users
|
3
|
+
def index
|
4
|
+
@users = User.all
|
5
|
+
display @users
|
6
|
+
end
|
7
|
+
|
8
|
+
# GET /users/1
|
9
|
+
def show(id)
|
10
|
+
@user = User.get(id)
|
11
|
+
raise NotFound unless @user
|
12
|
+
display @user
|
13
|
+
end
|
14
|
+
|
15
|
+
# GET /users/new
|
16
|
+
def new
|
17
|
+
only_provides :html
|
18
|
+
@user = User.new
|
19
|
+
display @user
|
20
|
+
end
|
21
|
+
|
22
|
+
# GET /users/1/edit
|
23
|
+
def edit(id)
|
24
|
+
only_provides :html
|
25
|
+
@user = User.get(id)
|
26
|
+
raise NotFound unless @user
|
27
|
+
display @user
|
28
|
+
end
|
29
|
+
|
30
|
+
# POST /users
|
31
|
+
def create(user)
|
32
|
+
@user = User.new(user)
|
33
|
+
if @user.save
|
34
|
+
redirect resource(@user), :message => {:notice => "User was successfully created"}
|
35
|
+
else
|
36
|
+
message[:error] = "User failed to be created"
|
37
|
+
render :new
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# PUT /users/1
|
42
|
+
def update(id, user)
|
43
|
+
@user = User.get(id)
|
44
|
+
raise NotFound unless @user
|
45
|
+
if @user.update_attributes(user)
|
46
|
+
redirect resource(@user)
|
47
|
+
else
|
48
|
+
display @user, :edit
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class User
|
2
|
+
include DataMapper::Resource
|
3
|
+
|
4
|
+
property :id, Serial
|
5
|
+
property :name, String
|
6
|
+
|
7
|
+
validates_present :name, :state, :access_state
|
8
|
+
|
9
|
+
state_machine :initial => :unregistered do
|
10
|
+
event :register do
|
11
|
+
transition :unregistered => :registered
|
12
|
+
end
|
13
|
+
|
14
|
+
event :unregister do
|
15
|
+
transition :registered => :unregistered
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
state_machine :access_state, :initial => :enabled do
|
20
|
+
event :enable do
|
21
|
+
transition all => :enabled
|
22
|
+
end
|
23
|
+
|
24
|
+
event :disable do
|
25
|
+
transition all => :disabled
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
<h1>Editing User</h1>
|
2
|
+
|
3
|
+
<%= form_for @user, :action => resource(@user) do %>
|
4
|
+
<%= error_messages %>
|
5
|
+
|
6
|
+
<p>
|
7
|
+
<%= text_field :name, :label => 'Name' %>
|
8
|
+
</p>
|
9
|
+
|
10
|
+
<p>
|
11
|
+
<%= label :state %><br />
|
12
|
+
<%= select :state_event, :selected => @user.state_event.to_s, :collection => @user.state_transitions, :value_method => :event, :text_method => :to_name, :prompt => @user.state_name.to_s %>
|
13
|
+
</p>
|
14
|
+
|
15
|
+
<p>
|
16
|
+
<%= label :access_state %><br />
|
17
|
+
<%= select :access_state_event, :selected => @user.access_state_event.to_s, :collection => @user.access_state_transitions, :value_method => :event, :text_method => :event, :prompt => "don't change" %>
|
18
|
+
</p>
|
19
|
+
|
20
|
+
<p><%= submit 'Update' %></p>
|
21
|
+
<% end =%>
|
22
|
+
|
23
|
+
<%= link_to 'Show', resource(@user) %> |
|
24
|
+
<%= link_to 'Back', resource(:users) %>
|
@@ -0,0 +1,23 @@
|
|
1
|
+
<h1>Listing Users</h1>
|
2
|
+
|
3
|
+
<table>
|
4
|
+
<tr>
|
5
|
+
<th>Name</th>
|
6
|
+
<th>State</th>
|
7
|
+
<th>Access State</th>
|
8
|
+
</tr>
|
9
|
+
|
10
|
+
<% @users.each do |user| %>
|
11
|
+
<tr>
|
12
|
+
<td><%=h user.name %></td>
|
13
|
+
<td><%=h user.state %></td>
|
14
|
+
<td><%=h user.access_state %></td>
|
15
|
+
<td><%= link_to 'Show', resource(user) %></td>
|
16
|
+
<td><%= link_to 'Edit', resource(user, :edit) %></td>
|
17
|
+
</tr>
|
18
|
+
<% end %>
|
19
|
+
</table>
|
20
|
+
|
21
|
+
<br />
|
22
|
+
|
23
|
+
<%= link_to 'New User', resource(:users, :new) %>
|
@@ -0,0 +1,17 @@
|
|
1
|
+
<p>
|
2
|
+
<b>Name:</b>
|
3
|
+
<%=h @user.name %>
|
4
|
+
</p>
|
5
|
+
|
6
|
+
<p>
|
7
|
+
<b>State:</b>
|
8
|
+
<%=h @user.state %>
|
9
|
+
</p>
|
10
|
+
|
11
|
+
<p>
|
12
|
+
<b>Access State:</b>
|
13
|
+
<%=h @user.access_state %>
|
14
|
+
</p>
|
15
|
+
|
16
|
+
<%= link_to 'Edit', resource(@user, :edit) %> |
|
17
|
+
<%= link_to 'Back', resource(:users) %>
|
@@ -0,0 +1,43 @@
|
|
1
|
+
class UsersController < ApplicationController
|
2
|
+
# GET /users
|
3
|
+
def index
|
4
|
+
@users = User.all
|
5
|
+
end
|
6
|
+
|
7
|
+
# GET /users/1
|
8
|
+
def show
|
9
|
+
@user = User.find(params[:id])
|
10
|
+
end
|
11
|
+
|
12
|
+
# GET /users/new
|
13
|
+
def new
|
14
|
+
@user = User.new
|
15
|
+
end
|
16
|
+
|
17
|
+
# GET /users/1/edit
|
18
|
+
def edit
|
19
|
+
@user = User.find(params[:id])
|
20
|
+
end
|
21
|
+
|
22
|
+
# POST /users
|
23
|
+
def create
|
24
|
+
@user = User.new(params[:user])
|
25
|
+
|
26
|
+
if @user.save
|
27
|
+
redirect_to(@user)
|
28
|
+
else
|
29
|
+
render :action => 'new'
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# PUT /users/1
|
34
|
+
def update
|
35
|
+
@user = User.find(params[:id])
|
36
|
+
|
37
|
+
if @user.update_attributes(params[:user])
|
38
|
+
redirect_to(@user)
|
39
|
+
else
|
40
|
+
render :action => 'edit'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class User < ActiveRecord::Base
|
2
|
+
validates_presence_of :name, :state, :access_state
|
3
|
+
|
4
|
+
state_machine :initial => :unregistered do
|
5
|
+
event :register do
|
6
|
+
transition :unregistered => :registered
|
7
|
+
end
|
8
|
+
|
9
|
+
event :unregister do
|
10
|
+
transition :registered => :unregistered
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
state_machine :access_state, :initial => :enabled do
|
15
|
+
event :enable do
|
16
|
+
transition all => :enabled
|
17
|
+
end
|
18
|
+
|
19
|
+
event :disable do
|
20
|
+
transition all => :disabled
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
<h1>Editing User</h1>
|
2
|
+
|
3
|
+
<% form_for(@user) do |f| %>
|
4
|
+
<%= f.error_messages %>
|
5
|
+
|
6
|
+
<p>
|
7
|
+
<%= f.label :name %><br />
|
8
|
+
<%= f.text_field :name %>
|
9
|
+
</p>
|
10
|
+
|
11
|
+
<p>
|
12
|
+
<%= f.label :state %><br />
|
13
|
+
<%= f.collection_select :state_event, @user.state_transitions, :event, :to_name, :include_blank => @user.state_name.to_s %>
|
14
|
+
</p>
|
15
|
+
|
16
|
+
<p>
|
17
|
+
<%= f.label :access_state %><br />
|
18
|
+
<%= f.collection_select :access_state_event, @user.access_state_transitions, :event, :event, :include_blank => "don't change" %>
|
19
|
+
</p>
|
20
|
+
|
21
|
+
<p><%= f.submit 'Update' %></p>
|
22
|
+
<% end %>
|
23
|
+
|
24
|
+
<%= link_to 'Show', @user %> |
|
25
|
+
<%= link_to 'Back', users_path %>
|
@@ -0,0 +1,23 @@
|
|
1
|
+
<h1>Listing Users</h1>
|
2
|
+
|
3
|
+
<table>
|
4
|
+
<tr>
|
5
|
+
<th>Name</th>
|
6
|
+
<th>State</th>
|
7
|
+
<th>Access State</th>
|
8
|
+
</tr>
|
9
|
+
|
10
|
+
<% @users.each do |user| %>
|
11
|
+
<tr>
|
12
|
+
<td><%=h user.name %></td>
|
13
|
+
<td><%=h user.state %></td>
|
14
|
+
<td><%=h user.access_state %></td>
|
15
|
+
<td><%= link_to 'Show', user %></td>
|
16
|
+
<td><%= link_to 'Edit', edit_user_path(user) %></td>
|
17
|
+
</tr>
|
18
|
+
<% end %>
|
19
|
+
</table>
|
20
|
+
|
21
|
+
<br />
|
22
|
+
|
23
|
+
<%= link_to 'New User', new_user_path %>
|