aasm 3.0.13 → 3.0.14

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.
@@ -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