aasm 3.0.16 → 3.0.17

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 (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