aasm 3.0.13 → 3.0.14

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,24 +1,28 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 3.0.14
4
+
5
+ * supporting event inspection for to-states transitions (`Event#transitions_to_state?`)
6
+
3
7
  ## 3.0.13
4
8
 
5
- * supporting ActiveRecord transactions when firing an event
9
+ * supporting *ActiveRecord* transactions when firing an event
6
10
 
7
11
  ## 3.0.12
8
12
 
9
- * aasm_from_states_for_state now supports to filter for specific transition
13
+ * `aasm_from_states_for_state` now supports to filter for specific transition
10
14
 
11
15
  ## 3.0.11
12
16
 
13
- * added class method aasm_from_states_for_state to retrieve all from states (regarding transitions) for a given state
17
+ * added class method `aasm_from_states_for_state` to retrieve all from states (regarding transitions) for a given state
14
18
 
15
19
  ## 3.0.10
16
20
 
17
- * added support for transitions from all other states (thanks to Stefan 'swrobel' Wrobel)
21
+ * added support for transitions from all other states (thanks to *Stefan 'swrobel' Wrobel*)
18
22
 
19
23
  ## 3.0.9
20
24
 
21
- * guard checks (e.g. may_edit?) now support guard parameters as well
25
+ * guard checks (e.g. `may_edit?`) now support guard parameters as well
22
26
 
23
27
  ## 3.0.8
24
28
 
@@ -58,7 +62,7 @@
58
62
  * whiny transactions: by default, raise an exception if an event transition is not possible
59
63
  * you may disable whiny transactions
60
64
 
61
- ## 2.4.0
65
+ ## 2.4.0
62
66
 
63
67
  * supporting new DSL (which is much shorter)
64
68
 
data/README.md CHANGED
@@ -2,170 +2,278 @@
2
2
 
3
3
  This package contains AASM, a library for adding finite state machines to Ruby classes.
4
4
 
5
- AASM started as the acts_as_state_machine plugin but has evolved into a more generic library
5
+ AASM started as the *acts_as_state_machine* plugin but has evolved into a more generic library
6
6
  that no longer targets only ActiveRecord models. It currently provides adapters for
7
7
  [ActiveRecord](http://api.rubyonrails.org/classes/ActiveRecord/Base.html) and
8
- [Mongoid](http://mongoid.org/), but it can be used for any Ruby class, no matter its
9
- parent class.
8
+ [Mongoid](http://mongoid.org/), but it can be used for any Ruby class, no matter what
9
+ parent class it has (if any).
10
10
 
11
- ### Transaction support
11
+ ## Usage
12
12
 
13
- Since version 3.0.13 AASM supports ActiveRecord transactions. So whenever a transition
14
- callback fails, all changes to any database record are rolled back.
13
+ Adding a state machine is as simple as including the AASM module and start defining
14
+ **states** and **events** together with their **transitions**:
15
15
 
16
- ## Features ##
16
+ ```ruby
17
+ class Job
18
+ include AASM
17
19
 
18
- * States
19
- * Machines
20
- * Events
21
- * Transitions
20
+ aasm do
21
+ state :sleeping, :initial => true
22
+ state :running
23
+ state :cleaning
22
24
 
23
- ## New Callbacks ##
25
+ event :run do
26
+ transitions :from => :sleeping, :to => :running
27
+ end
24
28
 
25
- The callback chain & order on a successful event looks like:
29
+ event :clean do
30
+ transitions :from => :running, :to => :cleaning
31
+ end
26
32
 
27
- oldstate:exit*
28
- event:before
29
- __find transition, if possible__
30
- transition:on_transition*
31
- oldstate:before_exit
32
- newstate:before_enter
33
- newstate:enter*
34
- __update state__
35
- event:success*
36
- oldstate:after_exit
37
- newstate:after_enter
38
- event:after
39
- obj:aasm_event_fired*
33
+ event :sleep do
34
+ transitions :from => [:running, :cleaning], :to => :sleeping
35
+ end
36
+ end
40
37
 
41
- (*) marks old callbacks
38
+ end
39
+ ```
42
40
 
41
+ This provides you with a couple of public methods for instances of the class `Job`:
43
42
 
44
- ## Installation ##
43
+ ```ruby
44
+ job = Job.new
45
+ job.sleeping? # => true
46
+ job.may_run? # => true
47
+ job.run
48
+ job.running? # => true
49
+ job.sleeping? # => false
50
+ job.may_run? # => false
51
+ job.run # => raises AASM::InvalidTransition
52
+ ```
45
53
 
46
- ### Manually from RubyGems.org ###
54
+ If you don't like exceptions and prefer a simple `true` or `false` as response, tell
55
+ AASM not to be *whiny*:
47
56
 
48
- ```sh
49
- % gem install aasm
57
+ ```ruby
58
+ class Job
59
+ ...
60
+ aasm :whiny_transitions => false do
61
+ ...
62
+ end
63
+ end
64
+
65
+ job.running? # => true
66
+ job.may_run? # => false
67
+ job.run # => false
50
68
  ```
51
69
 
52
- ### Or if you are using Bundler ###
70
+ ### Callbacks
71
+
72
+ You can define a number of callbacks for your transitions. These methods will be
73
+ called, when certain criteria are met, like entering a particular state:
53
74
 
54
75
  ```ruby
55
- # Gemfile
56
- gem 'aasm'
57
- ```
76
+ class Job
77
+ include AASM
58
78
 
59
- ### Building your own gems ###
79
+ aasm do
80
+ state :sleeping, :initial => true, :before_enter => :do_something
81
+ state :running
60
82
 
61
- ```sh
62
- % rake build
63
- % sudo gem install pkg/aasm-x.y.z.gem
83
+ event :run, :after => :notify_somebody do
84
+ transitions :from => :sleeping, :to => :running
85
+ end
86
+
87
+ event :sleep do
88
+ transitions :from => :running, :to => :sleeping
89
+ end
90
+ end
91
+
92
+ def do_something
93
+ ...
94
+ end
95
+
96
+ def notify_somebody
97
+ ...
98
+ end
99
+
100
+ end
64
101
  ```
65
102
 
66
- ## Examples ##
103
+ In this case `do_something` is called before actually entering the state `sleeping`,
104
+ while `notify_somebody` is called after the transition `run` (from `sleeping` to `running`)
105
+ is finished.
106
+
107
+ Here you can see a list of all possible callbacks, together with their order of calling:
67
108
 
68
- ### Simple Example ###
109
+ ```ruby
110
+ event:before
111
+ previous_state:before_exit
112
+ new_state:before_enter
113
+ ...update state...
114
+ previous_state:after_exit
115
+ new_state:after_enter
116
+ event:after
117
+ ```
69
118
 
70
- Here's a quick example highlighting some of the features.
119
+ ### Guards
120
+
121
+ Let's assume you want to allow particular transitions only if a defined condition is
122
+ given. For this you can set up a guard per transition, which will run before actually
123
+ running the transition. If the guard returns `false` the transition will be
124
+ denied (raising `AASM::InvalidTransition` or returning `false` itself):
71
125
 
72
126
  ```ruby
73
- class Conversation
127
+ class Job
74
128
  include AASM
75
129
 
76
- aasm :column => :current_state do # defaults to aasm_state
77
- state :unread, :initial => true
78
- state :read
79
- state :closed
130
+ aasm do
131
+ state :sleeping, :initial => true
132
+ state :running
133
+ state :cleaning
80
134
 
81
- event :view do
82
- transitions :to => :read, :from => [:unread]
135
+ event :run do
136
+ transitions :from => :sleeping, :to => :running
83
137
  end
84
138
 
85
- event :close do
86
- transitions :to => :closed, :from => [:read, :unread]
139
+ event :clean do
140
+ transitions :from => :running, :to => :cleaning
87
141
  end
142
+
143
+ event :sleep do
144
+ transitions :from => :running, :to => :sleeping, :guard => :cleaning_needed?
145
+ end
146
+ end
147
+
148
+ def cleaning_needed?
149
+ false
88
150
  end
89
151
 
90
152
  end
153
+
154
+ job = Job.new
155
+ job.run
156
+ job.may_sleep? # => false
157
+ job.sleep # => raises AASM::InvalidTransition
91
158
  ```
92
159
 
93
- ### A Slightly More Complex Example ###
94
160
 
95
- This example uses a few of the more complex features available.
161
+ ### ActiveRecord
162
+
163
+ AASM comes with support for ActiveRecord and allows automatical persisting of the object's
164
+ state in the database.
96
165
 
97
166
  ```ruby
98
- class Relationship
99
- include AASM
100
-
101
- aasm :column => :status do
102
- state :dating, :enter => :make_happy, :exit => :make_depressed
103
- state :intimate, :enter => :make_very_happy, :exit => :never_speak_again
104
- state :married, :enter => :give_up_intimacy, :exit => :buy_exotic_car_and_wear_a_combover
105
-
106
- event :get_intimate do
107
- transitions :to => :intimate, :from => [:dating], :guard => :drunk?
108
- end
109
-
110
- # Will allow transitioning from any state if guard allows it
111
- event :get_married do
112
- transitions :to => :married, :guard => :willing_to_give_up_manhood?
113
- end
167
+ class Job < ActiveRecord::Base
168
+ include AASM
169
+
170
+ aasm do # default column: aasm_state
171
+ state :sleeping, :initial => true
172
+ state :running
173
+
174
+ event :run do
175
+ transitions :from => :sleeping, :to => :running
176
+ end
177
+
178
+ event :sleep do
179
+ transitions :from => :running, :to => :sleeping
114
180
  end
115
- aasm_initial_state Proc.new { |relationship| relationship.strictly_for_fun? ? :intimate : :dating }
116
-
117
- def strictly_for_fun?; end
118
- def drunk?; end
119
- def willing_to_give_up_manhood?; end
120
- def make_happy; end
121
- def make_depressed; end
122
- def make_very_happy; end
123
- def never_speak_again; end
124
- def give_up_intimacy; end
125
- def buy_exotic_car_and_wear_a_combover; end
126
181
  end
182
+
183
+ end
184
+ ```
185
+
186
+ You can tell AASM to auto-save the object or leave it unsaved
187
+
188
+ ```ruby
189
+ job = Job.new
190
+ job.run # not saved
191
+ job.run! # saved
127
192
  ```
128
193
 
129
- ### Callbacks around events ###
194
+ Saving includes running all validations on the `Job` class. If you want make sure
195
+ the state gets saved without running validations (and thereby maybe persisting an
196
+ invalid object state), simply tell AASM to skip the validations:
197
+
130
198
  ```ruby
131
- class Relationship
132
- include AASM
133
-
134
- aasm do
135
- state :dating
136
- state :married
137
-
138
- event :get_married,
139
- :before => :make_vows,
140
- :after => :eat_wedding_cake do
141
- transitions :to => :married, :from => [:dating]
142
- end
199
+ class Job < ActiveRecord::Base
200
+ include AASM
201
+
202
+ aasm :skip_validation_on_save => true do
203
+ state :sleeping, :initial => true
204
+ state :running
205
+
206
+ event :run do
207
+ transitions :from => :sleeping, :to => :running
208
+ end
209
+
210
+ event :sleep do
211
+ transitions :from => :running, :to => :sleeping
143
212
  end
144
213
  end
214
+
215
+ end
145
216
  ```
146
217
 
147
- ### Persistence example ###
218
+ ### Transaction support
219
+
220
+ Since version *3.0.13* AASM supports ActiveRecord transactions. So whenever a transition
221
+ callback or the state update fails, all changes to any database record are rolled back.
222
+
223
+ ### Column name & migration
224
+
225
+ As a default AASM uses the column `aasm_state` to store the states. You can override
226
+ this by defining your favorite column name, using `:column` like this:
227
+
148
228
  ```ruby
149
- class InvalidPersistor < ActiveRecord::Base
150
- include AASM
151
- aasm :column => :status, :skip_validation_on_save => true do
152
- state :sleeping, :initial => true
153
- state :running
154
- event :run do
155
- transitions :to => :running, :from => :sleeping
156
- end
157
- event :sleep do
158
- transitions :to => :sleeping, :from => :running
159
- end
160
- end
161
- validates_presence_of :name
229
+ class Job < ActiveRecord::Base
230
+ include AASM
231
+
232
+ aasm :column => 'my_state' do
233
+ ...
162
234
  end
235
+
236
+ end
163
237
  ```
164
- This model can change AASM states which are stored into the database, even if the model itself is invalid!
165
238
 
239
+ Whatever column name is used, make sure to add a migration to provide this column
240
+ (of type `string`):
166
241
 
242
+ ```ruby
243
+ class AddJobState < ActiveRecord::Migration
244
+ def self.up
245
+ add_column :jobs, :aasm_state, :string
246
+ end
247
+
248
+ def self.down
249
+ remove_column :job, :aasm_state
250
+ end
251
+ end
252
+ ```
253
+
254
+ ## Installation ##
255
+
256
+ ### Manually from RubyGems.org ###
257
+
258
+ ```sh
259
+ % gem install aasm
260
+ ```
261
+
262
+ ### Or if you are using Bundler ###
263
+
264
+ ```ruby
265
+ # Gemfile
266
+ gem 'aasm'
267
+ ```
268
+
269
+ ### Building your own gems ###
270
+
271
+ ```sh
272
+ % rake build
273
+ % sudo gem install pkg/aasm-x.y.z.gem
274
+ ```
167
275
 
168
- ## Changelog ##
276
+ ## Latest changes ##
169
277
 
170
278
  Look at the [CHANGELOG](https://github.com/aasm/aasm/blob/master/CHANGELOG.md) for details.
171
279
 
@@ -21,6 +21,7 @@ Gem::Specification.new do |s|
21
21
  s.add_development_dependency 'shoulda'
22
22
  s.add_development_dependency 'sqlite3'
23
23
  s.add_development_dependency 'minitest'
24
+ # s.add_development_dependency 'debugger'
24
25
  s.add_development_dependency 'ruby-debug-completion'
25
26
 
26
27
  s.files = `git ls-files`.split("\n")
@@ -6,6 +6,8 @@ module AASM
6
6
  # Use a fancier auto-loading thingy, perhaps. When there are more persistence engines.
7
7
  hierarchy = base.ancestors.map {|klass| klass.to_s}
8
8
 
9
+ require File.join(File.dirname(__FILE__), 'persistence', 'base')
10
+ require File.join(File.dirname(__FILE__), 'persistence', 'read_state')
9
11
  if hierarchy.include?("ActiveRecord::Base")
10
12
  require File.join(File.dirname(__FILE__), 'persistence', 'active_record_persistence')
11
13
  base.send(:include, AASM::Persistence::ActiveRecordPersistence)
@@ -32,9 +32,10 @@ module AASM
32
32
  # end
33
33
  #
34
34
  def self.included(base)
35
+ base.extend AASM::Persistence::Base::ClassMethods
35
36
  base.extend AASM::Persistence::ActiveRecordPersistence::ClassMethods
36
37
  base.send(:include, AASM::Persistence::ActiveRecordPersistence::InstanceMethods)
37
- base.send(:include, AASM::Persistence::ActiveRecordPersistence::ReadState) unless base.method_defined?(:aasm_read_state)
38
+ base.send(:include, AASM::Persistence::ReadState) unless base.method_defined?(:aasm_read_state)
38
39
  base.send(:include, AASM::Persistence::ActiveRecordPersistence::WriteState) unless base.method_defined?(:aasm_write_state)
39
40
  base.send(:include, AASM::Persistence::ActiveRecordPersistence::WriteStateWithoutPersistence) unless base.method_defined?(:aasm_write_state_without_persistence)
40
41
 
@@ -46,41 +47,6 @@ module AASM
46
47
  end
47
48
 
48
49
  module ClassMethods
49
- # Maps to the aasm_column in the database. Defaults to "aasm_state". You can write:
50
- #
51
- # create_table :foos do |t|
52
- # t.string :name
53
- # t.string :aasm_state
54
- # end
55
- #
56
- # class Foo < ActiveRecord::Base
57
- # include AASM
58
- # end
59
- #
60
- # OR:
61
- #
62
- # create_table :foos do |t|
63
- # t.string :name
64
- # t.string :status
65
- # end
66
- #
67
- # class Foo < ActiveRecord::Base
68
- # include AASM
69
- # aasm_column :status
70
- # end
71
- #
72
- # This method is both a getter and a setter
73
- def aasm_column(column_name=nil)
74
- if column_name
75
- AASM::StateMachine[self].config.column = column_name.to_sym
76
- # @aasm_column = column_name.to_sym
77
- else
78
- AASM::StateMachine[self].config.column ||= :aasm_state
79
- # @aasm_column ||= :aasm_state
80
- end
81
- # @aasm_column
82
- AASM::StateMachine[self].config.column
83
- end
84
50
 
85
51
  def find_in_state(number, state, *args)
86
52
  with_state_scope state do
@@ -203,41 +169,6 @@ module AASM
203
169
  end
204
170
  end
205
171
 
206
- module ReadState
207
-
208
- # Returns the value of the aasm_column - called from <tt>aasm_current_state</tt>
209
- #
210
- # If it's a new record, and the aasm state column is blank it returns the initial state:
211
- #
212
- # class Foo < ActiveRecord::Base
213
- # include AASM
214
- # aasm_column :status
215
- # aasm_state :opened
216
- # aasm_state :closed
217
- # end
218
- #
219
- # foo = Foo.new
220
- # foo.current_state # => :opened
221
- # foo.close
222
- # foo.current_state # => :closed
223
- #
224
- # foo = Foo.find(1)
225
- # foo.current_state # => :opened
226
- # foo.aasm_state = nil
227
- # foo.current_state # => nil
228
- #
229
- # NOTE: intended to be called from an event
230
- #
231
- # This allows for nil aasm states - be sure to add validation to your model
232
- def aasm_read_state
233
- if new_record?
234
- send(self.class.aasm_column).blank? ? aasm_determine_state_name(self.class.aasm_initial_state) : send(self.class.aasm_column).to_sym
235
- else
236
- send(self.class.aasm_column).nil? ? nil : send(self.class.aasm_column).to_sym
237
- end
238
- end
239
- end
240
-
241
172
  end
242
173
  end
243
174
  end