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.
Files changed (56) hide show
  1. data/CHANGELOG.rdoc +31 -1
  2. data/README.rdoc +33 -21
  3. data/Rakefile +2 -2
  4. data/examples/merb-rest/controller.rb +51 -0
  5. data/examples/merb-rest/model.rb +28 -0
  6. data/examples/merb-rest/view_edit.html.erb +24 -0
  7. data/examples/merb-rest/view_index.html.erb +23 -0
  8. data/examples/merb-rest/view_new.html.erb +13 -0
  9. data/examples/merb-rest/view_show.html.erb +17 -0
  10. data/examples/rails-rest/controller.rb +43 -0
  11. data/examples/rails-rest/migration.rb +11 -0
  12. data/examples/rails-rest/model.rb +23 -0
  13. data/examples/rails-rest/view_edit.html.erb +25 -0
  14. data/examples/rails-rest/view_index.html.erb +23 -0
  15. data/examples/rails-rest/view_new.html.erb +14 -0
  16. data/examples/rails-rest/view_show.html.erb +17 -0
  17. data/lib/state_machine/assertions.rb +2 -2
  18. data/lib/state_machine/callback.rb +14 -8
  19. data/lib/state_machine/condition_proxy.rb +3 -3
  20. data/lib/state_machine/event.rb +19 -21
  21. data/lib/state_machine/event_collection.rb +114 -0
  22. data/lib/state_machine/extensions.rb +127 -11
  23. data/lib/state_machine/guard.rb +1 -1
  24. data/lib/state_machine/integrations/active_record/locale.rb +2 -1
  25. data/lib/state_machine/integrations/active_record.rb +117 -39
  26. data/lib/state_machine/integrations/data_mapper/observer.rb +20 -64
  27. data/lib/state_machine/integrations/data_mapper.rb +71 -26
  28. data/lib/state_machine/integrations/sequel.rb +69 -21
  29. data/lib/state_machine/machine.rb +267 -139
  30. data/lib/state_machine/machine_collection.rb +145 -0
  31. data/lib/state_machine/matcher.rb +2 -2
  32. data/lib/state_machine/node_collection.rb +9 -4
  33. data/lib/state_machine/state.rb +22 -32
  34. data/lib/state_machine/state_collection.rb +66 -17
  35. data/lib/state_machine/transition.rb +259 -28
  36. data/lib/state_machine.rb +121 -56
  37. data/tasks/state_machine.rake +1 -0
  38. data/tasks/state_machine.rb +26 -0
  39. data/test/active_record.log +116877 -0
  40. data/test/functional/state_machine_test.rb +118 -12
  41. data/test/sequel.log +28542 -0
  42. data/test/unit/callback_test.rb +46 -1
  43. data/test/unit/condition_proxy_test.rb +55 -28
  44. data/test/unit/event_collection_test.rb +228 -0
  45. data/test/unit/event_test.rb +51 -46
  46. data/test/unit/integrations/active_record_test.rb +128 -70
  47. data/test/unit/integrations/data_mapper_test.rb +150 -58
  48. data/test/unit/integrations/sequel_test.rb +63 -6
  49. data/test/unit/invalid_event_test.rb +7 -0
  50. data/test/unit/machine_collection_test.rb +678 -0
  51. data/test/unit/machine_test.rb +198 -91
  52. data/test/unit/node_collection_test.rb +33 -30
  53. data/test/unit/state_collection_test.rb +112 -5
  54. data/test/unit/state_test.rb +23 -3
  55. data/test/unit/transition_test.rb +750 -89
  56. metadata +28 -3
data/CHANGELOG.rdoc CHANGED
@@ -1,6 +1,36 @@
1
1
  == master
2
2
 
3
- == 0.6.3 / 2008-03-10
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 :hood_state, :initial => :closed, :namespace => 'hood' do
129
- event :open do
130
- transition all => :opened
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 :close do
134
- transition all => :closed
136
+ event :disable do
137
+ transition all => :off
135
138
  end
136
139
 
137
- state :opened, :value => 1
138
- state :closed, :value => 0
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.next_ignite_transition # => #<StateMachine::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>
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) # => true
193
- vehicle.state?(:invalid) # => ArgumentError: :invalid is an invalid name
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.hood_state # => 0
197
- vehicle.hood_state_name # => :closed
201
+ vehicle.alarm_state # => 1
202
+ vehicle.alarm_state_name # => :active
198
203
 
199
- vehicle.can_open_hood? # => true
200
- vehicle.open_hood # => true
201
- vehicle.hood_state # => 1
202
- vehicle.hood_state_name # => :opened
203
- vehicle.can_close_hood? # => true
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.hood_opened? # => true
206
- vehicle.hood_closed? # => false
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, saved|
311
- Audit.log(self, transition) if saved # self is the record
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.6.3'
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,13 @@
1
+ <h1>New User</h1>
2
+
3
+ <%= form_for @user, :action => resource(:users) do %>
4
+ <%= error_messages %>
5
+
6
+ <p>
7
+ <%= text_field :name, :label => 'Name' %>
8
+ </p>
9
+
10
+ <p><%= submit 'Create' %></p>
11
+ <% end =%>
12
+
13
+ <%= link_to 'Back', resource(:users) %>
@@ -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,11 @@
1
+ class CreateUsers < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :users do |t|
4
+ t.string :name, :state, :access_state
5
+ end
6
+ end
7
+
8
+ def self.down
9
+ drop_table :users
10
+ end
11
+ 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 %>