aasm 3.0.16 → 3.0.17

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. data/.gitignore +1 -0
  2. data/.travis.yml +2 -1
  3. data/API +34 -0
  4. data/CHANGELOG.md +7 -0
  5. data/Gemfile +1 -1
  6. data/HOWTO +12 -0
  7. data/README.md +57 -4
  8. data/aasm.gemspec +2 -0
  9. data/lib/aasm.rb +5 -4
  10. data/lib/aasm/aasm.rb +50 -75
  11. data/lib/aasm/base.rb +22 -18
  12. data/lib/aasm/event.rb +130 -0
  13. data/lib/aasm/instance_base.rb +87 -0
  14. data/lib/aasm/localizer.rb +54 -0
  15. data/lib/aasm/persistence.rb +22 -14
  16. data/lib/aasm/persistence/active_record_persistence.rb +38 -69
  17. data/lib/aasm/persistence/base.rb +42 -2
  18. data/lib/aasm/persistence/mongoid_persistence.rb +33 -64
  19. data/lib/aasm/state.rb +78 -0
  20. data/lib/aasm/state_machine.rb +2 -2
  21. data/lib/aasm/transition.rb +49 -0
  22. data/lib/aasm/version.rb +1 -1
  23. data/spec/models/active_record/api.rb +75 -0
  24. data/spec/models/auth_machine.rb +1 -1
  25. data/spec/models/bar.rb +15 -0
  26. data/spec/models/foo.rb +34 -0
  27. data/spec/models/mongoid/simple_mongoid.rb +10 -0
  28. data/spec/models/mongoid/{mongoid_models.rb → simple_new_dsl_mongoid.rb} +1 -12
  29. data/spec/models/persistence.rb +2 -1
  30. data/spec/models/this_name_better_not_be_in_use.rb +11 -0
  31. data/spec/schema.rb +1 -1
  32. data/spec/spec_helper.rb +8 -1
  33. data/spec/unit/api_spec.rb +72 -0
  34. data/spec/unit/callbacks_spec.rb +2 -2
  35. data/spec/unit/event_spec.rb +269 -0
  36. data/spec/unit/inspection_spec.rb +43 -5
  37. data/spec/unit/{supporting_classes/localizer_spec.rb → localizer_spec.rb} +2 -2
  38. data/spec/unit/memory_leak_spec.rb +12 -12
  39. data/spec/unit/persistence/active_record_persistence_spec.rb +0 -40
  40. data/spec/unit/persistence/mongoid_persistance_spec.rb +3 -2
  41. data/spec/unit/simple_example_spec.rb +6 -0
  42. data/spec/unit/{supporting_classes/state_spec.rb → state_spec.rb} +2 -2
  43. data/spec/unit/{supporting_classes/state_transition_spec.rb → transition_spec.rb} +18 -18
  44. metadata +127 -38
  45. data/lib/aasm/persistence/read_state.rb +0 -40
  46. data/lib/aasm/supporting_classes/event.rb +0 -146
  47. data/lib/aasm/supporting_classes/localizer.rb +0 -56
  48. data/lib/aasm/supporting_classes/state.rb +0 -80
  49. data/lib/aasm/supporting_classes/state_transition.rb +0 -51
  50. data/spec/spec_helpers/models_spec_helper.rb +0 -64
  51. data/spec/unit/supporting_classes/event_spec.rb +0 -203
data/.gitignore CHANGED
@@ -10,3 +10,4 @@ spec/debug.log
10
10
  spec/*.db
11
11
  TODO
12
12
  .rvmrc
13
+ alto
@@ -3,8 +3,9 @@ rvm:
3
3
  - 1.8.7
4
4
  - 1.9.2
5
5
  - 1.9.3
6
+ - 2.0.0
6
7
  # - jruby-18mode # JRuby in 1.8 mode
7
8
  # - jruby-19mode # JRuby in 1.9 mode
8
9
  - rbx-18mode
9
10
  - rbx-19mode
10
- services: mongodb
11
+ services: mongodb
data/API ADDED
@@ -0,0 +1,34 @@
1
+
2
+ Overwrite method to read the current state. Used to provide another storage mechanism,
3
+ different from the standard Rails read_attribute method.
4
+
5
+ class MyClass
6
+ include AASM
7
+
8
+ def aasm_read_state
9
+ # retrieve the current state manually
10
+ end
11
+ end
12
+
13
+
14
+ Overwrite method to write the current state (and actually persist it). Used to provide
15
+ another storage mechanism, different from the standard Rails write_attribute method.
16
+
17
+ class MyClass
18
+ include AASM
19
+
20
+ def aasm_write_state
21
+ # store and persist the current state manually
22
+ end
23
+ end
24
+
25
+
26
+ Overwrite method to write the current state (without persisting it).
27
+
28
+ class MyClass
29
+ include AASM
30
+
31
+ def aasm_write_state_without_persistence
32
+ # store the current state manually
33
+ end
34
+ end
@@ -1,5 +1,12 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## Unreleased
4
+
5
+ ## 3.0.17
6
+
7
+ * supporting instance level inspection for states (including permissible state, see issue #54)
8
+ * added autocreation of constants for each state ([@jherdman](https://github.com/jherdman))
9
+
3
10
  ## 3.0.16
4
11
 
5
12
  * added autocreation of state scopes for Mongoid (thanks to [@jonnyshields](https://github.com/johnnyshields))
data/Gemfile CHANGED
@@ -1,3 +1,3 @@
1
- source :rubygems
1
+ source 'https://rubygems.org'
2
2
 
3
3
  gemspec
data/HOWTO ADDED
@@ -0,0 +1,12 @@
1
+ How to
2
+
3
+ 1. Run tests for Mongoid
4
+
5
+ Start MongoDB
6
+
7
+ $> mongod
8
+
9
+ Run the specs
10
+
11
+ $> rspec spec/unit/persistence/mongoid_persistance_spec.rb
12
+
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # AASM - Ruby state machines [![Build Status](https://secure.travis-ci.org/aasm/aasm.png)](http://travis-ci.org/aasm/aasm) [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/aasm/aasm)
1
+ # AASM - Ruby state machines [![Build Status](https://secure.travis-ci.org/aasm/aasm.png)](http://travis-ci.org/aasm/aasm) [![Code Climate](https://codeclimate.com/github/aasm/aasm.png)](https://codeclimate.com/github/aasm/aasm) [![Coverage Status](https://coveralls.io/repos/aasm/aasm/badge.png?branch=master)](https://coveralls.io/r/aasm/aasm)
2
2
 
3
3
  This package contains AASM, a library for adding finite state machines to Ruby classes.
4
4
 
@@ -81,14 +81,24 @@ class Job
81
81
  state :running
82
82
 
83
83
  event :run, :after => :notify_somebody do
84
- transitions :from => :sleeping, :to => :running
84
+ transitions :from => :sleeping, :to => :running, :on_transition => Proc.new {|obj, *args| obj.set_process(*args) }
85
85
  end
86
86
 
87
87
  event :sleep do
88
+ after do
89
+ ...
90
+ end
91
+ error do |e|
92
+ ...
93
+ end
88
94
  transitions :from => :running, :to => :sleeping
89
95
  end
90
96
  end
91
97
 
98
+ def set_process(name)
99
+ ...
100
+ end
101
+
92
102
  def do_something
93
103
  ...
94
104
  end
@@ -116,6 +126,18 @@ Here you can see a list of all possible callbacks, together with their order of
116
126
  event:after
117
127
  ```
118
128
 
129
+ Also, you can pass parameters to events:
130
+
131
+ ```ruby
132
+ job = Job.new
133
+ job.run(:running, :defragmentation)
134
+ ```
135
+
136
+ In this case the `set_process` would be called with `:defagmentation` argument.
137
+
138
+ In case of an error during the event processing the error is rescued and passed to `:error`
139
+ callback, which can handle it or re-raise it for further propagation.
140
+
119
141
  ### Guards
120
142
 
121
143
  Let's assume you want to allow particular transitions only if a defined condition is
@@ -277,12 +299,36 @@ class AddJobState < ActiveRecord::Migration
277
299
  end
278
300
 
279
301
  def self.down
280
- remove_column :job, :aasm_state
302
+ remove_column :jobs, :aasm_state
281
303
  end
282
304
  end
283
305
  ```
284
306
 
285
- ## Installation ##
307
+ ### <a id="inspection">Inspection
308
+
309
+ AASM supports a couple of methods to find out which states or events are provided or permissible.
310
+
311
+ Given the `Job` class from above:
312
+
313
+ ```ruby
314
+ job = Job.new
315
+
316
+ job.states
317
+ => [:sleeping, :running, :cleaning]
318
+
319
+ job.states(:permissible => true)
320
+ => [:running]
321
+ job.run
322
+ job.states(:permissible => true)
323
+ => [:cleaning, :sleeping]
324
+
325
+ job.events
326
+ => [:run, :clean, :sleep]
327
+ ```
328
+
329
+
330
+
331
+ ## <a id="installation">Installation ##
286
332
 
287
333
  ### Manually from RubyGems.org ###
288
334
 
@@ -308,6 +354,13 @@ gem 'aasm'
308
354
 
309
355
  Look at the [CHANGELOG](https://github.com/aasm/aasm/blob/master/CHANGELOG.md) for details.
310
356
 
357
+ ## Questions? ##
358
+
359
+ Feel free to
360
+
361
+ * [create an issue on GitHub](https://github.com/aasm/aasm/issues)
362
+ * [ask a question on StackOverflow](http://stackoverflow.com) (tag with `aasm`)
363
+ * send us a tweet [@aasm](http://twitter.com/aasm)
311
364
 
312
365
  ## Authors ##
313
366
 
@@ -23,7 +23,9 @@ Gem::Specification.new do |s|
23
23
  s.add_development_dependency 'sqlite3'
24
24
  s.add_development_dependency 'minitest'
25
25
  # s.add_development_dependency 'debugger'
26
+ # s.add_development_dependency 'pry'
26
27
  s.add_development_dependency 'ruby-debug-completion'
28
+ s.add_development_dependency 'coveralls'
27
29
 
28
30
  s.files = `git ls-files`.split("\n")
29
31
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
@@ -4,10 +4,11 @@ require 'ostruct'
4
4
  require File.join(File.dirname(__FILE__), 'aasm', 'version')
5
5
  require File.join(File.dirname(__FILE__), 'aasm', 'errors')
6
6
  require File.join(File.dirname(__FILE__), 'aasm', 'base')
7
- require File.join(File.dirname(__FILE__), 'aasm', 'supporting_classes', 'state_transition')
8
- require File.join(File.dirname(__FILE__), 'aasm', 'supporting_classes', 'event')
9
- require File.join(File.dirname(__FILE__), 'aasm', 'supporting_classes', 'state')
10
- require File.join(File.dirname(__FILE__), 'aasm', 'supporting_classes', 'localizer')
7
+ require File.join(File.dirname(__FILE__), 'aasm', 'instance_base')
8
+ require File.join(File.dirname(__FILE__), 'aasm', 'transition')
9
+ require File.join(File.dirname(__FILE__), 'aasm', 'event')
10
+ require File.join(File.dirname(__FILE__), 'aasm', 'state')
11
+ require File.join(File.dirname(__FILE__), 'aasm', 'localizer')
11
12
  require File.join(File.dirname(__FILE__), 'aasm', 'state_machine')
12
13
  require File.join(File.dirname(__FILE__), 'aasm', 'persistence')
13
14
  require File.join(File.dirname(__FILE__), 'aasm', 'aasm')
@@ -3,18 +3,19 @@ module AASM
3
3
  def self.included(base) #:nodoc:
4
4
  base.extend AASM::ClassMethods
5
5
  AASM::StateMachine[base] ||= AASM::StateMachine.new('')
6
- AASM::Persistence.set_persistence(base)
6
+ AASM::Persistence.load_persistence(base)
7
7
  super
8
8
  end
9
9
 
10
10
  module ClassMethods
11
11
 
12
12
  # make sure inheritance (aka subclassing) works with AASM
13
- def inherited(klass)
14
- AASM::StateMachine[klass] = AASM::StateMachine[self].clone
13
+ def inherited(base)
14
+ AASM::StateMachine[base] = AASM::StateMachine[self].clone
15
15
  super
16
16
  end
17
17
 
18
+ # this is the entry point for all state and event definitions
18
19
  def aasm(options={}, &block)
19
20
  @aasm ||= AASM::Base.new(self, options)
20
21
  @aasm.instance_eval(&block) if block # new DSL
@@ -31,6 +32,7 @@ module AASM
31
32
  end
32
33
  end
33
34
 
35
+ # is this better?: aasm.states.name.from_states
34
36
  def aasm_from_states_for_state(state, options={})
35
37
  if options[:transition]
36
38
  aasm.events[options[:transition]].transitions_to_state(state).flatten.map(&:from).flatten
@@ -71,100 +73,73 @@ module AASM
71
73
 
72
74
  # aasm.event(:event_name).human?
73
75
  def aasm_human_event_name(event) # event_name?
74
- AASM::SupportingClasses::Localizer.new.human_event_name(self, event)
76
+ AASM::Localizer.new.human_event_name(self, event)
75
77
  end
76
78
  end # ClassMethods
77
79
 
78
- # this method does what? does it deliver the current state?
79
- def aasm_current_state
80
- @aasm_current_state ||=
81
- aasm_persistable? ? aasm_read_state : aasm_enter_initial_state
82
- end
83
-
84
- # private?
85
- def aasm_enter_initial_state
86
- state_name = aasm_determine_state_name(self.class.aasm_initial_state)
87
- state = aasm_state_object_for_state(state_name)
88
-
89
- state.fire_callbacks(:before_enter, self)
90
- state.fire_callbacks(:enter, self)
91
- self.aasm_current_state = state_name
92
- state.fire_callbacks(:after_enter, self)
93
-
94
- state_name
80
+ def aasm
81
+ @aasm ||= AASM::InstanceBase.new(self)
95
82
  end
96
83
 
97
- # private?
98
- def aasm_events_for_current_state
99
- aasm_events_for_state(aasm_current_state)
84
+ # may be overwritten by persistence mixins
85
+ def aasm_read_state
86
+ aasm.enter_initial_state
100
87
  end
101
88
 
102
- # filters the results of events_for_current_state so that only those that
103
- # are really currently possible (given transition guards) are shown.
104
- def aasm_permissible_events_for_current_state
105
- aasm_events_for_current_state.select{ |e| self.send(("may_" + e.to_s + "?").to_sym) }
89
+ # may be overwritten by persistence mixins
90
+ def aasm_write_state(new_state)
91
+ true
106
92
  end
107
93
 
108
- def aasm_events_for_state(state)
109
- events = self.class.aasm_events.values.select {|event| event.transitions_from_state?(state) }
110
- events.map {|event| event.name}
94
+ # may be overwritten by persistence mixins
95
+ def aasm_write_state_without_persistence(new_state)
96
+ true
111
97
  end
112
98
 
113
- def aasm_human_state
114
- AASM::SupportingClasses::Localizer.new.human_state_name(self.class, aasm_current_state)
99
+ # deprecated
100
+ def aasm_current_state
101
+ # warn "#aasm_current_state is deprecated and will be removed in version 3.2.0; please use #aasm.state instead!"
102
+ aasm.current_state
115
103
  end
116
104
 
117
- private
118
-
119
- def aasm_persistable?
120
- self.respond_to?(:aasm_read_state) || self.private_methods.include?('aasm_read_state')
105
+ # deprecated
106
+ def aasm_enter_initial_state
107
+ # warn "#aasm_enter_initial_state is deprecated and will be removed in version 3.2.0; please use #aasm.enter_initial_state instead!"
108
+ aasm.enter_initial_state
121
109
  end
122
110
 
123
- def aasm_set_current_state_with_persistence(state)
124
- save_success = true
125
- if self.respond_to?(:aasm_write_state) || self.private_methods.include?('aasm_write_state')
126
- save_success = aasm_write_state(state)
127
- end
128
- self.aasm_current_state = state if save_success
129
-
130
- save_success
111
+ # deprecated
112
+ def aasm_events_for_current_state
113
+ # warn "#aasm_events_for_current_state is deprecated and will be removed in version 3.2.0; please use #aasm.events instead!"
114
+ aasm.events(aasm.current_state)
131
115
  end
132
116
 
133
- def aasm_current_state=(state)
134
- if self.respond_to?(:aasm_write_state_without_persistence) || self.private_methods.include?('aasm_write_state_without_persistence')
135
- aasm_write_state_without_persistence(state)
136
- end
137
- @aasm_current_state = state
117
+ # deprecated
118
+ def aasm_permissible_events_for_current_state
119
+ # warn "#aasm_permissible_events_for_current_state is deprecated and will be removed in version 3.2.0; please use #aasm.permissible_events instead!"
120
+ aasm.permissible_events
138
121
  end
139
122
 
140
- def aasm_determine_state_name(state)
141
- case state
142
- when Symbol, String
143
- state
144
- when Proc
145
- state.call(self)
146
- else
147
- raise NotImplementedError, "Unrecognized state-type given. Expected Symbol, String, or Proc."
148
- end
123
+ # deprecated
124
+ def aasm_events_for_state(state_name)
125
+ # warn "#aasm_events_for_state(state_name) is deprecated and will be removed in version 3.2.0; please use #aasm.events(state_name) instead!"
126
+ aasm.events(state_name)
149
127
  end
150
128
 
151
- def aasm_state_object_for_state(name)
152
- obj = self.class.aasm_states.find {|s| s == name}
153
- raise AASM::UndefinedState, "State :#{name} doesn't exist" if obj.nil?
154
- obj
129
+ # deprecated
130
+ def aasm_human_state
131
+ # warn "#aasm_human_state is deprecated and will be removed in version 3.2.0; please use #aasm.human_state instead!"
132
+ aasm.human_state
155
133
  end
156
134
 
157
- def aasm_may_fire_event?(name, *args)
158
- event = self.class.aasm_events[name]
159
- event.may_fire?(self, *args)
160
- end
135
+ private
161
136
 
162
137
  def aasm_fire_event(name, options, *args)
163
138
  persist = options[:persist]
164
139
 
165
140
  event = self.class.aasm_events[name]
166
141
  begin
167
- old_state = aasm_state_object_for_state(aasm_current_state)
142
+ old_state = aasm.state_object_for_name(aasm.current_state)
168
143
 
169
144
 
170
145
  old_state.fire_callbacks(:exit, self)
@@ -173,7 +148,7 @@ private
173
148
  event.fire_callbacks(:before, self)
174
149
 
175
150
  if new_state_name = event.fire(self, *args)
176
- new_state = aasm_state_object_for_state(new_state_name)
151
+ new_state = aasm.state_object_for_name(new_state_name)
177
152
 
178
153
  # new before_ callbacks
179
154
  old_state.fire_callbacks(:before_exit, self)
@@ -183,10 +158,10 @@ private
183
158
 
184
159
  persist_successful = true
185
160
  if persist
186
- persist_successful = aasm_set_current_state_with_persistence(new_state_name)
187
- event.execute_success_callback(self) if persist_successful
161
+ persist_successful = aasm.set_current_state_with_persistence(new_state_name)
162
+ event.fire_callbacks(:success, self) if persist_successful
188
163
  else
189
- self.aasm_current_state = new_state_name
164
+ aasm.current_state = new_state_name
190
165
  end
191
166
 
192
167
  if persist_successful
@@ -194,7 +169,7 @@ private
194
169
  new_state.fire_callbacks(:after_enter, self)
195
170
  event.fire_callbacks(:after, self)
196
171
 
197
- self.aasm_event_fired(name, old_state.name, self.aasm_current_state) if self.respond_to?(:aasm_event_fired)
172
+ self.aasm_event_fired(name, old_state.name, aasm.current_state) if self.respond_to?(:aasm_event_fired)
198
173
  else
199
174
  self.aasm_event_failed(name, old_state.name) if self.respond_to?(:aasm_event_failed)
200
175
  end
@@ -207,13 +182,13 @@ private
207
182
  end
208
183
 
209
184
  if AASM::StateMachine[self.class].config.whiny_transitions
210
- raise AASM::InvalidTransition, "Event '#{event.name}' cannot transition from '#{self.aasm_current_state}'"
185
+ raise AASM::InvalidTransition, "Event '#{event.name}' cannot transition from '#{aasm.current_state}'"
211
186
  else
212
187
  false
213
188
  end
214
189
  end
215
190
  rescue StandardError => e
216
- event.execute_error_callback(self, e)
191
+ event.fire_callbacks(:error, self, e) || raise(e)
217
192
  end
218
193
  end
219
194
  end
@@ -3,50 +3,54 @@ module AASM
3
3
 
4
4
  def initialize(clazz, options={}, &block)
5
5
  @clazz = clazz
6
- sm = AASM::StateMachine[@clazz]
7
- sm.config.column = options[:column].to_sym if options[:column]
6
+ @state_machine = AASM::StateMachine[@clazz]
7
+ @state_machine.config.column = options[:column].to_sym if options[:column]
8
8
 
9
9
  if options.key?(:whiny_transitions)
10
- sm.config.whiny_transitions = options[:whiny_transitions]
11
- elsif sm.config.whiny_transitions.nil?
12
- sm.config.whiny_transitions = true # this is the default, so let's cry
10
+ @state_machine.config.whiny_transitions = options[:whiny_transitions]
11
+ elsif @state_machine.config.whiny_transitions.nil?
12
+ @state_machine.config.whiny_transitions = true # this is the default, so let's cry
13
13
  end
14
14
 
15
15
  if options.key?(:skip_validation_on_save)
16
- sm.config.skip_validation_on_save = options[:skip_validation_on_save]
17
- elsif sm.config.skip_validation_on_save.nil?
18
- sm.config.skip_validation_on_save = false # this is the default, so don't store any new state if the model is invalid
16
+ @state_machine.config.skip_validation_on_save = options[:skip_validation_on_save]
17
+ elsif @state_machine.config.skip_validation_on_save.nil?
18
+ @state_machine.config.skip_validation_on_save = false # this is the default, so don't store any new state if the model is invalid
19
19
  end
20
20
  end
21
21
 
22
22
  def initial_state
23
- AASM::StateMachine[@clazz].initial_state
23
+ @state_machine.initial_state
24
24
  end
25
25
 
26
+ # define a state
26
27
  def state(name, options={})
27
28
  # @clazz.aasm_state(name, options)
28
- sm = AASM::StateMachine[@clazz]
29
- sm.create_state(name, @clazz, options)
30
- sm.initial_state = name if options[:initial] || !sm.initial_state
29
+ @state_machine.add_state(name, @clazz, options)
30
+ @state_machine.initial_state = name if options[:initial] || !@state_machine.initial_state
31
31
 
32
32
  @clazz.send(:define_method, "#{name.to_s}?") do
33
33
  aasm_current_state == name
34
34
  end
35
+
36
+ unless @clazz.const_defined?("STATE_#{name.to_s.upcase}")
37
+ @clazz.const_set("STATE_#{name.to_s.upcase}", name)
38
+ end
35
39
  end
36
40
 
41
+ # define an event
37
42
  def event(name, options={}, &block)
38
43
  # @clazz.aasm_event(name, options, &block)
39
- sm = AASM::StateMachine[@clazz]
40
44
 
41
- unless sm.events.has_key?(name)
42
- sm.events[name] = AASM::SupportingClasses::Event.new(name, options, &block)
45
+ unless @state_machine.events.has_key?(name)
46
+ @state_machine.events[name] = AASM::Event.new(name, options, &block)
43
47
  end
44
48
 
45
49
  # an addition over standard aasm so that, before firing an event, you can ask
46
50
  # may_event? and get back a boolean that tells you whether the guard method
47
51
  # on the transition will let this happen.
48
52
  @clazz.send(:define_method, "may_#{name.to_s}?") do |*args|
49
- aasm_may_fire_event?(name, *args)
53
+ aasm.may_fire_event?(name, *args)
50
54
  end
51
55
 
52
56
  @clazz.send(:define_method, "#{name.to_s}!") do |*args|
@@ -59,11 +63,11 @@ module AASM
59
63
  end
60
64
 
61
65
  def states
62
- AASM::StateMachine[@clazz].states
66
+ @state_machine.states
63
67
  end
64
68
 
65
69
  def events
66
- AASM::StateMachine[@clazz].events
70
+ @state_machine.events
67
71
  end
68
72
 
69
73
  def states_for_select