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
@@ -1,40 +0,0 @@
1
- module AASM
2
- module Persistence
3
- module ReadState
4
-
5
- # Returns the value of the aasm_column - called from <tt>aasm_current_state</tt>
6
- #
7
- # If it's a new record, and the aasm state column is blank it returns the initial state
8
- # (example provided here for ActiveRecord, but it's true for Mongoid as well):
9
- #
10
- # class Foo < ActiveRecord::Base
11
- # include AASM
12
- # aasm_column :status
13
- # aasm_state :opened
14
- # aasm_state :closed
15
- # end
16
- #
17
- # foo = Foo.new
18
- # foo.current_state # => :opened
19
- # foo.close
20
- # foo.current_state # => :closed
21
- #
22
- # foo = Foo.find(1)
23
- # foo.current_state # => :opened
24
- # foo.aasm_state = nil
25
- # foo.current_state # => nil
26
- #
27
- # NOTE: intended to be called from an event
28
- #
29
- # This allows for nil aasm states - be sure to add validation to your model
30
- def aasm_read_state
31
- if new_record?
32
- send(self.class.aasm_column).blank? ? aasm_determine_state_name(self.class.aasm_initial_state) : send(self.class.aasm_column).to_sym
33
- else
34
- send(self.class.aasm_column).nil? ? nil : send(self.class.aasm_column).to_sym
35
- end
36
- end
37
-
38
- end # ReadState
39
- end # Persistence
40
- end # AASM
@@ -1,146 +0,0 @@
1
- module AASM
2
- module SupportingClasses
3
- class Event
4
- attr_reader :name, :success, :options
5
-
6
- def initialize(name, options = {}, &block)
7
- @name = name
8
- @transitions = []
9
- update(options, &block)
10
- end
11
-
12
- # a neutered version of fire - it doesn't actually fire the event, it just
13
- # executes the transition guards to determine if a transition is even
14
- # an option given current conditions.
15
- def may_fire?(obj, to_state=nil, *args)
16
- _fire(obj, true, to_state, *args) # true indicates test firing
17
- end
18
-
19
- def fire(obj, to_state=nil, *args)
20
- _fire(obj, false, to_state, *args) # false indicates this is not a test (fire!)
21
- end
22
-
23
- def transitions_from_state?(state)
24
- transitions_from_state(state).any?
25
- end
26
-
27
- def transitions_from_state(state)
28
- @transitions.select { |t| t.from == state }
29
- end
30
-
31
- def transitions_to_state?(state)
32
- transitions_to_state(state).any?
33
- end
34
-
35
- def transitions_to_state(state)
36
- @transitions.select { |t| t.to == state }
37
- end
38
-
39
- def all_transitions
40
- @transitions
41
- end
42
-
43
- def fire_callbacks(action, record)
44
- action = @options[action]
45
- action.is_a?(Array) ?
46
- action.each {|a| _fire_callbacks(a, record)} :
47
- _fire_callbacks(action, record)
48
- end
49
-
50
- def ==(event)
51
- if event.is_a? Symbol
52
- name == event
53
- else
54
- name == event.name
55
- end
56
- end
57
-
58
- def execute_success_callback(obj, success = nil)
59
- callback = success || @success
60
- case(callback)
61
- when String, Symbol
62
- obj.send(callback)
63
- when Proc
64
- callback.call(obj)
65
- when Array
66
- callback.each{|meth|self.execute_success_callback(obj, meth)}
67
- end
68
- end
69
-
70
- def execute_error_callback(obj, error, error_callback=nil)
71
- callback = error_callback || @error
72
- raise error unless callback
73
- case(callback)
74
- when String, Symbol
75
- raise NoMethodError unless obj.respond_to?(callback.to_sym)
76
- obj.send(callback, error)
77
- when Proc
78
- callback.call(obj, error)
79
- when Array
80
- callback.each{|meth|self.execute_error_callback(obj, error, meth)}
81
- end
82
- end
83
-
84
- private
85
-
86
- def update(options = {}, &block)
87
- if options.key?(:success) then
88
- @success = options[:success]
89
- end
90
- if options.key?(:error) then
91
- @error = options[:error]
92
- end
93
- if block then
94
- instance_eval(&block)
95
- end
96
- @options = options
97
- self
98
- end
99
-
100
- # Execute if test? == false, otherwise return true/false depending on whether it would fire
101
- def _fire(obj, test, to_state=nil, *args)
102
- if @transitions.map(&:from).any?
103
- transitions = @transitions.select { |t| t.from == obj.aasm_current_state }
104
- return nil if transitions.size == 0
105
- else
106
- transitions = @transitions
107
- end
108
-
109
- result = test ? false : nil
110
- transitions.each do |transition|
111
- next if to_state and !Array(transition.to).include?(to_state)
112
- if transition.perform(obj, *args)
113
- if test
114
- result = true
115
- else
116
- result = to_state || Array(transition.to).first
117
- transition.execute(obj, *args)
118
- end
119
-
120
- break
121
- end
122
- end
123
- result
124
- end
125
-
126
- def _fire_callbacks(action, record)
127
- case action
128
- when Symbol, String
129
- record.send(action)
130
- when Proc
131
- action.call(record)
132
- end
133
- end
134
-
135
- def transitions(trans_opts)
136
- # Create a separate transition for each from state to the given state
137
- Array(trans_opts[:from]).each do |s|
138
- @transitions << AASM::SupportingClasses::StateTransition.new(trans_opts.merge({:from => s.to_sym}))
139
- end
140
- # Create a transition if to is specified without from (transitions from ANY state)
141
- @transitions << AASM::SupportingClasses::StateTransition.new(trans_opts) if @transitions.empty? && trans_opts[:to]
142
- end
143
-
144
- end
145
- end # SupportingClasses
146
- end # AASM
@@ -1,56 +0,0 @@
1
- module AASM
2
- module SupportingClasses
3
- class Localizer
4
- def human_event_name(klass, event)
5
- checklist = ancestors_list(klass).inject([]) do |list, ancestor|
6
- list << :"#{i18n_scope(klass)}.events.#{i18n_klass(ancestor)}.#{event}"
7
- list
8
- end
9
- translate_queue(checklist) || I18n.translate(checklist.shift, :default => event.to_s.humanize)
10
- end
11
-
12
- def human_state_name(klass, state)
13
- checklist = ancestors_list(klass).inject([]) do |list, ancestor|
14
- list << item_for(klass, state, ancestor)
15
- list << item_for(klass, state, ancestor, :old_style => true)
16
- list
17
- end
18
- translate_queue(checklist) || I18n.translate(checklist.shift, :default => state.to_s.humanize)
19
- end
20
-
21
- private
22
-
23
- def item_for(klass, state, ancestor, options={})
24
- separator = options[:old_style] ? '.' : '/'
25
- :"#{i18n_scope(klass)}.attributes.#{i18n_klass(ancestor)}.#{klass.aasm_column}#{separator}#{state}"
26
- end
27
-
28
- def translate_queue(checklist)
29
- (0...(checklist.size-1)).each do |i|
30
- begin
31
- return I18n.translate(checklist.shift, :raise => true)
32
- rescue I18n::MissingTranslationData
33
- # that's okay
34
- end
35
- end
36
- nil
37
- end
38
-
39
- # added for rails 2.x compatibility
40
- def i18n_scope(klass)
41
- klass.respond_to?(:i18n_scope) ? klass.i18n_scope : :activerecord
42
- end
43
-
44
- # added for rails < 3.0.3 compatibility
45
- def i18n_klass(klass)
46
- klass.model_name.respond_to?(:i18n_key) ? klass.model_name.i18n_key : klass.name.underscore
47
- end
48
-
49
- def ancestors_list(klass)
50
- klass.ancestors.select do |ancestor|
51
- ancestor.respond_to?(:model_name) unless ancestor == ActiveRecord::Base
52
- end
53
- end
54
- end
55
- end # SupportingClasses
56
- end # AASM
@@ -1,80 +0,0 @@
1
- module AASM
2
- module SupportingClasses
3
- class State
4
- attr_reader :name, :options
5
-
6
- def initialize(name, clazz, options={})
7
- @name = name
8
- @clazz = clazz
9
- update(options)
10
- end
11
-
12
- def ==(state)
13
- if state.is_a? Symbol
14
- name == state
15
- else
16
- name == state.name
17
- end
18
- end
19
-
20
- def <=>(state)
21
- if state.is_a? Symbol
22
- name <=> state
23
- else
24
- name <=> state.name
25
- end
26
- end
27
-
28
- def to_s
29
- name.to_s
30
- end
31
-
32
- def fire_callbacks(action, record)
33
- action = @options[action]
34
- catch :halt_aasm_chain do
35
- action.is_a?(Array) ?
36
- action.each {|a| _fire_callbacks(a, record)} :
37
- _fire_callbacks(action, record)
38
- end
39
- end
40
-
41
- def display_name
42
- @display_name ||= begin
43
- if Module.const_defined?(:I18n)
44
- localized_name
45
- else
46
- name.to_s.gsub(/_/, ' ').capitalize
47
- end
48
- end
49
- end
50
-
51
- def localized_name
52
- AASM::SupportingClasses::Localizer.new.human_state_name(@clazz, self)
53
- end
54
-
55
- def for_select
56
- [display_name, name.to_s]
57
- end
58
-
59
- private
60
-
61
- def update(options = {})
62
- if options.key?(:display) then
63
- @display_name = options.delete(:display)
64
- end
65
- @options = options
66
- self
67
- end
68
-
69
- def _fire_callbacks(action, record)
70
- case action
71
- when Symbol, String
72
- record.send(action)
73
- when Proc
74
- action.call(record)
75
- end
76
- end
77
-
78
- end
79
- end # SupportingClasses
80
- end # AASM
@@ -1,51 +0,0 @@
1
- module AASM
2
- module SupportingClasses
3
- class StateTransition
4
- attr_reader :from, :to, :opts
5
- alias_method :options, :opts
6
-
7
- def initialize(opts)
8
- @from, @to, @guard, @on_transition = opts[:from], opts[:to], opts[:guard], opts[:on_transition]
9
- @opts = opts
10
- end
11
-
12
- # TODO: should be named allowed? or similar
13
- def perform(obj, *args)
14
- case @guard
15
- when Symbol, String
16
- obj.send(@guard, *args)
17
- when Proc
18
- @guard.call(obj, *args)
19
- else
20
- true
21
- end
22
- end
23
-
24
- def execute(obj, *args)
25
- @on_transition.is_a?(Array) ?
26
- @on_transition.each {|ot| _execute(obj, ot, *args)} :
27
- _execute(obj, @on_transition, *args)
28
- end
29
-
30
- def ==(obj)
31
- @from == obj.from && @to == obj.to
32
- end
33
-
34
- def from?(value)
35
- @from == value
36
- end
37
-
38
- private
39
-
40
- def _execute(obj, on_transition, *args)
41
- case on_transition
42
- when Proc
43
- on_transition.arity == 0 ? on_transition.call : on_transition.call(obj, *args)
44
- when Symbol, String
45
- obj.send(:method, on_transition.to_sym).arity == 0 ? obj.send(on_transition) : obj.send(on_transition, *args)
46
- end
47
- end
48
-
49
- end
50
- end # SupportingClasses
51
- end # AASM
@@ -1,64 +0,0 @@
1
- Dir[File.dirname(__FILE__) + "/../models/*.rb"].sort.each { |f| require File.expand_path(f) }
2
-
3
- class Foo
4
- include AASM
5
- aasm do
6
- state :open, :initial => true, :exit => :exit
7
- state :closed, :enter => :enter
8
-
9
- event :close, :success => :success_callback do
10
- transitions :to => :closed, :from => [:open]
11
- end
12
-
13
- event :null do
14
- transitions :to => :closed, :from => [:open], :guard => :always_false
15
- end
16
- end
17
-
18
- def always_false
19
- false
20
- end
21
-
22
- def success_callback
23
- end
24
-
25
- def enter
26
- end
27
- def exit
28
- end
29
- end
30
-
31
- class FooTwo < Foo
32
- include AASM
33
- aasm do
34
- state :foo
35
- end
36
- end
37
-
38
- class Bar
39
- include AASM
40
-
41
- aasm do
42
- state :read
43
- state :ended
44
-
45
- event :foo do
46
- transitions :to => :ended, :from => [:read]
47
- end
48
- end
49
- end
50
-
51
- class Baz < Bar
52
- end
53
-
54
- class ThisNameBetterNotBeInUse
55
- include AASM
56
-
57
- aasm do
58
- state :initial
59
- state :symbol
60
- state :string
61
- state :array
62
- state :proc
63
- end
64
- end
@@ -1,203 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe 'adding an event' do
4
- let(:event) do
5
- AASM::SupportingClasses::Event.new(:close_order, {:success => :success_callback}) do
6
- transitions :to => :closed, :from => [:open, :received]
7
- end
8
- end
9
-
10
- it 'should set the name' do
11
- event.name.should == :close_order
12
- end
13
-
14
- it 'should set the success callback' do
15
- event.success.should == :success_callback
16
- end
17
-
18
- it 'should create transitions' do
19
- transitions = event.all_transitions
20
- transitions[0].from.should == :open
21
- transitions[0].to.should == :closed
22
- transitions[1].from.should == :received
23
- transitions[1].to.should == :closed
24
- end
25
- end
26
-
27
- describe 'transition inspection' do
28
- let(:event) do
29
- AASM::SupportingClasses::Event.new(:run) do
30
- transitions :to => :running, :from => :sleeping
31
- end
32
- end
33
-
34
- it 'should support inspecting transitions from other states' do
35
- event.transitions_from_state(:sleeping).map(&:to).should == [:running]
36
- event.transitions_from_state?(:sleeping).should be_true
37
-
38
- event.transitions_from_state(:cleaning).map(&:to).should == []
39
- event.transitions_from_state?(:cleaning).should be_false
40
- end
41
-
42
- it 'should support inspecting transitions to other states' do
43
- event.transitions_to_state(:running).map(&:from).should == [:sleeping]
44
- event.transitions_to_state?(:running).should be_true
45
-
46
- event.transitions_to_state(:cleaning).map(&:to).should == []
47
- event.transitions_to_state?(:cleaning).should be_false
48
- end
49
- end
50
-
51
- describe 'firing an event' do
52
- it 'should return nil if the transitions are empty' do
53
- obj = mock('object')
54
- obj.stub!(:aasm_current_state)
55
-
56
- event = AASM::SupportingClasses::Event.new(:event)
57
- event.fire(obj).should be_nil
58
- end
59
-
60
- it 'should return the state of the first matching transition it finds' do
61
- event = AASM::SupportingClasses::Event.new(:event) do
62
- transitions :to => :closed, :from => [:open, :received]
63
- end
64
-
65
- obj = mock('object')
66
- obj.stub!(:aasm_current_state).and_return(:open)
67
-
68
- event.fire(obj).should == :closed
69
- end
70
-
71
- it 'should call the guard with the params passed in' do
72
- event = AASM::SupportingClasses::Event.new(:event) do
73
- transitions :to => :closed, :from => [:open, :received], :guard => :guard_fn
74
- end
75
-
76
- obj = mock('object')
77
- obj.stub!(:aasm_current_state).and_return(:open)
78
- obj.should_receive(:guard_fn).with('arg1', 'arg2').and_return(true)
79
-
80
- event.fire(obj, nil, 'arg1', 'arg2').should == :closed
81
- end
82
-
83
- end
84
-
85
- describe 'executing the success callback' do
86
-
87
- it "should send the success callback if it's a symbol" do
88
- ThisNameBetterNotBeInUse.instance_eval {
89
- aasm_event :with_symbol, :success => :symbol_success_callback do
90
- transitions :to => :symbol, :from => [:initial]
91
- end
92
- }
93
-
94
- model = ThisNameBetterNotBeInUse.new
95
- model.should_receive(:symbol_success_callback)
96
- model.with_symbol!
97
- end
98
-
99
- it "should send the success callback if it's a string" do
100
- ThisNameBetterNotBeInUse.instance_eval {
101
- aasm_event :with_string, :success => 'string_success_callback' do
102
- transitions :to => :string, :from => [:initial]
103
- end
104
- }
105
-
106
- model = ThisNameBetterNotBeInUse.new
107
- model.should_receive(:string_success_callback)
108
- model.with_string!
109
- end
110
-
111
- it "should call each success callback if passed an array of strings and/or symbols" do
112
- ThisNameBetterNotBeInUse.instance_eval {
113
- aasm_event :with_array, :success => [:success_callback1, 'success_callback2'] do
114
- transitions :to => :array, :from => [:initial]
115
- end
116
- }
117
-
118
- model = ThisNameBetterNotBeInUse.new
119
- model.should_receive(:success_callback1)
120
- model.should_receive(:success_callback2)
121
- model.with_array!
122
- end
123
-
124
- it "should call each success callback if passed an array of strings and/or symbols and/or procs" do
125
- ThisNameBetterNotBeInUse.instance_eval {
126
- aasm_event :with_array_including_procs, :success => [:success_callback1, 'success_callback2', lambda { |obj| obj.proc_success_callback }] do
127
- transitions :to => :array, :from => [:initial]
128
- end
129
- }
130
-
131
- model = ThisNameBetterNotBeInUse.new
132
- model.should_receive(:success_callback1)
133
- model.should_receive(:success_callback2)
134
- model.should_receive(:proc_success_callback)
135
- model.with_array_including_procs!
136
- end
137
-
138
- it "should call the success callback if it's a proc" do
139
- ThisNameBetterNotBeInUse.instance_eval {
140
- aasm_event :with_proc, :success => lambda { |obj| obj.proc_success_callback } do
141
- transitions :to => :proc, :from => [:initial]
142
- end
143
- }
144
-
145
- model = ThisNameBetterNotBeInUse.new
146
- model.should_receive(:proc_success_callback)
147
- model.with_proc!
148
- end
149
- end
150
-
151
- describe 'parametrised events' do
152
- let(:pe) {ParametrisedEvent.new}
153
-
154
- it 'should transition to specified next state (sleeping to showering)' do
155
- pe.wakeup!(:showering)
156
- pe.aasm_current_state.should == :showering
157
- end
158
-
159
- it 'should transition to specified next state (sleeping to working)' do
160
- pe.wakeup!(:working)
161
- pe.aasm_current_state.should == :working
162
- end
163
-
164
- it 'should transition to default (first or showering) state' do
165
- pe.wakeup!
166
- pe.aasm_current_state.should == :showering
167
- end
168
-
169
- it 'should transition to default state when on_transition invoked' do
170
- pe.dress!(nil, 'purple', 'dressy')
171
- pe.aasm_current_state.should == :working
172
- end
173
-
174
- it 'should call on_transition method with args' do
175
- pe.wakeup!(:showering)
176
- pe.should_receive(:wear_clothes).with('blue', 'jeans')
177
- pe.dress!(:working, 'blue', 'jeans')
178
- end
179
-
180
- it 'should call on_transition proc' do
181
- pe.wakeup!(:showering)
182
- pe.should_receive(:wear_clothes).with('purple', 'slacks')
183
- pe.dress!(:dating, 'purple', 'slacks')
184
- end
185
-
186
- it 'should call on_transition with an array of methods' do
187
- pe.wakeup!(:showering)
188
- pe.should_receive(:condition_hair)
189
- pe.should_receive(:fix_hair)
190
- pe.dress!(:prettying_up)
191
- end
192
- end
193
-
194
- describe 'event firing without persistence' do
195
- it 'should attempt to persist if aasm_write_state is defined' do
196
- foo = Foo.new
197
- def foo.aasm_write_state; end
198
- foo.should be_open
199
-
200
- foo.should_receive(:aasm_write_state_without_persistence)
201
- foo.close
202
- end
203
- end