state_machine 0.6.3 → 0.7.0
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/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 %>
|