aasm 3.0.24 → 3.4.0

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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +6 -0
  3. data/.travis.yml +29 -4
  4. data/CHANGELOG.md +56 -0
  5. data/Gemfile +10 -1
  6. data/LICENSE +1 -1
  7. data/README.md +151 -20
  8. data/aasm.gemspec +5 -6
  9. data/gemfiles/rails_3.2.gemfile +13 -0
  10. data/gemfiles/rails_4.0.gemfile +16 -0
  11. data/gemfiles/rails_4.1.gemfile +16 -0
  12. data/lib/aasm/aasm.rb +36 -32
  13. data/lib/aasm/base.rb +49 -31
  14. data/lib/aasm/event.rb +28 -17
  15. data/lib/aasm/instance_base.rb +9 -4
  16. data/lib/aasm/localizer.rb +1 -1
  17. data/lib/aasm/persistence/active_record_persistence.rb +65 -16
  18. data/lib/aasm/persistence/base.rb +10 -14
  19. data/lib/aasm/persistence/mongoid_persistence.rb +10 -8
  20. data/lib/aasm/persistence/sequel_persistence.rb +108 -0
  21. data/lib/aasm/persistence.rb +3 -0
  22. data/lib/aasm/state.rb +4 -3
  23. data/lib/aasm/state_machine.rb +18 -10
  24. data/lib/aasm/transition.rb +13 -6
  25. data/lib/aasm/version.rb +1 -1
  26. data/lib/aasm.rb +0 -3
  27. data/spec/database.rb +33 -0
  28. data/spec/models/double_definer.rb +21 -0
  29. data/spec/models/foo.rb +2 -1
  30. data/spec/models/guardian.rb +48 -0
  31. data/spec/models/mongoid/no_scope_mongoid.rb +1 -1
  32. data/spec/models/mongoid/simple_mongoid.rb +5 -4
  33. data/spec/models/mongoid/simple_new_dsl_mongoid.rb +1 -1
  34. data/spec/models/not_auto_loaded/process.rb +10 -8
  35. data/spec/models/persistence.rb +5 -13
  36. data/spec/spec_helper.rb +1 -1
  37. data/spec/unit/api_spec.rb +12 -12
  38. data/spec/unit/callbacks_spec.rb +29 -45
  39. data/spec/unit/complex_example_spec.rb +24 -15
  40. data/spec/unit/event_naming_spec.rb +24 -0
  41. data/spec/unit/event_spec.rb +124 -76
  42. data/spec/unit/guard_spec.rb +60 -0
  43. data/spec/unit/initial_state_spec.rb +4 -5
  44. data/spec/unit/inspection_spec.rb +42 -53
  45. data/spec/unit/localizer_spec.rb +22 -18
  46. data/spec/unit/memory_leak_spec.rb +2 -2
  47. data/spec/unit/new_dsl_spec.rb +2 -2
  48. data/spec/unit/persistence/active_record_persistence_spec.rb +357 -89
  49. data/spec/unit/persistence/mongoid_persistance_spec.rb +102 -81
  50. data/spec/unit/persistence/sequel_persistence_spec.rb +103 -0
  51. data/spec/unit/reloading_spec.rb +15 -0
  52. data/spec/unit/simple_example_spec.rb +20 -21
  53. data/spec/unit/state_spec.rb +16 -16
  54. data/spec/unit/subclassing_spec.rb +8 -8
  55. data/spec/unit/transition_spec.rb +59 -44
  56. metadata +38 -96
  57. data/lib/aasm/deprecated/aasm.rb +0 -15
  58. data/spec/models/callback_old_dsl.rb +0 -41
  59. data/spec/schema.rb +0 -35
data/lib/aasm/aasm.rb CHANGED
@@ -5,7 +5,7 @@ module AASM
5
5
 
6
6
  # do not overwrite existing state machines, which could have been created by
7
7
  # inheritance, see class method inherited
8
- AASM::StateMachine[base] ||= AASM::StateMachine.new('')
8
+ AASM::StateMachine[base] ||= AASM::StateMachine.new
9
9
 
10
10
  AASM::Persistence.load_persistence(base)
11
11
  super
@@ -26,52 +26,56 @@ module AASM
26
26
  @aasm
27
27
  end
28
28
 
29
- # TODO: maybe better: aasm.initial_state
29
+ # TODO remove this method in v4.0.0
30
30
  def aasm_initial_state(set_state=nil)
31
31
  if set_state
32
- # deprecated way to set the value
32
+ warn ".aasm_initial_state(:name) is deprecated and will be removed in version 4.0.0; please use .aasm.initial_state = :name instead!"
33
33
  AASM::StateMachine[self].initial_state = set_state
34
34
  else
35
+ warn ".aasm_initial_state is deprecated and will be removed in version 4.0.0; please use .aasm.initial_state instead!"
35
36
  AASM::StateMachine[self].initial_state
36
37
  end
37
38
  end
38
39
 
39
- # is this better?: aasm.states.name.from_states
40
+ # TODO remove this method in v4.0.0
40
41
  def aasm_from_states_for_state(state, options={})
41
- if options[:transition]
42
- aasm.events[options[:transition]].transitions_to_state(state).flatten.map(&:from).flatten
43
- else
44
- aasm.events.map {|k,v| v.transitions_to_state(state)}.flatten.map(&:from).flatten
45
- end
42
+ warn ".aasm_from_states_for_state is deprecated and will be removed in version 4.0.0; please use .aasm.from_states_for_state instead!"
43
+ aasm.from_states_for_state(state, options)
46
44
  end
47
45
 
48
- # deprecated
46
+ # TODO remove this method in v4.0.0
49
47
  def aasm_initial_state=(state)
48
+ warn ".aasm_initial_state= is deprecated and will be removed in version 4.0.0"
50
49
  AASM::StateMachine[self].initial_state = state
51
50
  end
52
51
 
53
- # deprecated
52
+ # TODO remove this method in v4.0.0
54
53
  def aasm_state(name, options={})
54
+ warn ".aasm_state is deprecated and will be removed in version 4.0.0; please use .aasm.state instead!"
55
55
  aasm.state(name, options)
56
56
  end
57
57
 
58
- # deprecated
58
+ # TODO remove this method in v4.0.0
59
59
  def aasm_event(name, options = {}, &block)
60
+ warn ".aasm_event is deprecated and will be removed in version 4.0.0; please use .aasm.event instead!"
60
61
  aasm.event(name, options, &block)
61
62
  end
62
63
 
63
- # deprecated
64
+ # TODO remove this method in v4.0.0
64
65
  def aasm_states
66
+ warn ".aasm_states is deprecated and will be removed in version 4.0.0; please use .aasm.states instead!"
65
67
  aasm.states
66
68
  end
67
69
 
68
- # deprecated
70
+ # TODO remove this method in v4.0.0
69
71
  def aasm_events
72
+ warn ".aasm_events is deprecated and will be removed in version 4.0.0; please use .aasm.events instead!"
70
73
  aasm.events
71
74
  end
72
75
 
73
- # deprecated
76
+ # TODO remove this method in v4.0.0
74
77
  def aasm_states_for_select
78
+ warn ".aasm_states_for_select is deprecated and will be removed in version 4.0.0; please use .aasm.states_for_select instead!"
75
79
  aasm.states_for_select
76
80
  end
77
81
 
@@ -103,46 +107,46 @@ module AASM
103
107
  true
104
108
  end
105
109
 
106
- # deprecated
110
+ # TODO remove this method in v4.0.0
107
111
  def aasm_current_state
108
- # warn "#aasm_current_state is deprecated and will be removed in version 3.2.0; please use #aasm.state instead!"
112
+ warn "#aasm_current_state is deprecated and will be removed in version 4.0.0; please use #aasm.current_state instead!"
109
113
  aasm.current_state
110
114
  end
111
115
 
112
- # deprecated
116
+ # TODO remove this method in v4.0.0
113
117
  def aasm_enter_initial_state
114
- # warn "#aasm_enter_initial_state is deprecated and will be removed in version 3.2.0; please use #aasm.enter_initial_state instead!"
118
+ warn "#aasm_enter_initial_state is deprecated and will be removed in version 4.0.0; please use #aasm.enter_initial_state instead!"
115
119
  aasm.enter_initial_state
116
120
  end
117
121
 
118
- # deprecated
122
+ # TODO remove this method in v4.0.0
119
123
  def aasm_events_for_current_state
120
- # warn "#aasm_events_for_current_state is deprecated and will be removed in version 3.2.0; please use #aasm.events instead!"
124
+ warn "#aasm_events_for_current_state is deprecated and will be removed in version 4.0.0; please use #aasm.events(aasm.current_state) instead!"
121
125
  aasm.events(aasm.current_state)
122
126
  end
123
127
 
124
- # deprecated
128
+ # TODO remove this method in v4.0.0
125
129
  def aasm_permissible_events_for_current_state
126
- # warn "#aasm_permissible_events_for_current_state is deprecated and will be removed in version 3.2.0; please use #aasm.permissible_events instead!"
130
+ warn "#aasm_permissible_events_for_current_state is deprecated and will be removed in version 4.0.0; please use #aasm.permissible_events instead!"
127
131
  aasm.permissible_events
128
132
  end
129
133
 
130
- # deprecated
134
+ # TODO remove this method in v4.0.0
131
135
  def aasm_events_for_state(state_name)
132
- # 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!"
136
+ warn "#aasm_events_for_state(state_name) is deprecated and will be removed in version 4.0.0; please use #aasm.events(state_name) instead!"
133
137
  aasm.events(state_name)
134
138
  end
135
139
 
136
- # deprecated
140
+ # TODO remove this method in v4.0.0
137
141
  def aasm_human_state
138
- # warn "#aasm_human_state is deprecated and will be removed in version 3.2.0; please use #aasm.human_state instead!"
142
+ warn "#aasm_human_state is deprecated and will be removed in version 4.0.0; please use #aasm.human_state instead!"
139
143
  aasm.human_state
140
144
  end
141
145
 
142
146
  private
143
147
 
144
148
  def aasm_fire_event(event_name, options, *args, &block)
145
- event = self.class.aasm_events[event_name]
149
+ event = self.class.aasm.events[event_name]
146
150
  begin
147
151
  old_state = aasm.state_object_for_name(aasm.current_state)
148
152
  old_state.fire_callbacks(:exit, self)
@@ -151,16 +155,16 @@ private
151
155
  event.fire_callbacks(:before, self)
152
156
 
153
157
  if new_state_name = event.fire(self, *args)
154
- fired(event, old_state, new_state_name, options, &block)
158
+ aasm_fired(event, old_state, new_state_name, options, &block)
155
159
  else
156
- failed(event_name, old_state)
160
+ aasm_failed(event_name, old_state)
157
161
  end
158
162
  rescue StandardError => e
159
163
  event.fire_callbacks(:error, self, e) || raise(e)
160
164
  end
161
165
  end
162
166
 
163
- def fired(event, old_state, new_state_name, options)
167
+ def aasm_fired(event, old_state, new_state_name, options)
164
168
  persist = options[:persist]
165
169
 
166
170
  new_state = aasm.state_object_for_name(new_state_name)
@@ -196,7 +200,7 @@ private
196
200
  persist_successful
197
201
  end
198
202
 
199
- def failed(event_name, old_state)
203
+ def aasm_failed(event_name, old_state)
200
204
  if self.respond_to?(:aasm_event_failed)
201
205
  self.aasm_event_failed(event_name, old_state.name)
202
206
  end
data/lib/aasm/base.rb CHANGED
@@ -1,67 +1,66 @@
1
1
  module AASM
2
2
  class Base
3
3
 
4
- def initialize(clazz, options={}, &block)
5
- @clazz = clazz
6
- @state_machine = AASM::StateMachine[@clazz]
4
+ def initialize(klass, options={}, &block)
5
+ @klass = klass
6
+ @state_machine = AASM::StateMachine[@klass]
7
7
  @state_machine.config.column = options[:column].to_sym if options[:column]
8
+ @options = options
8
9
 
9
- if options.key?(:whiny_transitions)
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
- end
10
+ # let's cry if the transition is invalid
11
+ configure :whiny_transitions, true
14
12
 
15
- if options.key?(:create_scopes)
16
- @state_machine.config.create_scopes = options[:create_scopes]
17
- elsif @state_machine.config.create_scopes.nil?
18
- @state_machine.config.create_scopes = true # this is the default, so let's create scopes
19
- end
13
+ # create named scopes for each state
14
+ configure :create_scopes, true
20
15
 
21
- if options.key?(:skip_validation_on_save)
22
- @state_machine.config.skip_validation_on_save = options[:skip_validation_on_save]
23
- elsif @state_machine.config.skip_validation_on_save.nil?
24
- @state_machine.config.skip_validation_on_save = false # this is the default, so don't store any new state if the model is invalid
25
- end
16
+ # don't store any new state if the model is invalid
17
+ configure :skip_validation_on_save, false
18
+
19
+ # use requires_new for nested transactions
20
+ configure :requires_new_transaction, true
21
+
22
+ configure :enum, nil
26
23
  end
27
24
 
28
- def initial_state
29
- @state_machine.initial_state
25
+ def initial_state(new_initial_state=nil)
26
+ if new_initial_state
27
+ @state_machine.initial_state = new_initial_state
28
+ else
29
+ @state_machine.initial_state
30
+ end
30
31
  end
31
32
 
32
33
  # define a state
33
34
  def state(name, options={})
34
- # @clazz.aasm_state(name, options)
35
- @state_machine.add_state(name, @clazz, options)
36
- @state_machine.initial_state = name if options[:initial] || !@state_machine.initial_state
35
+ @state_machine.add_state(name, @klass, options)
37
36
 
38
- @clazz.send(:define_method, "#{name.to_s}?") do
37
+ @klass.send(:define_method, "#{name.to_s}?") do
39
38
  aasm.current_state == name
40
39
  end
41
40
 
42
- unless @clazz.const_defined?("STATE_#{name.to_s.upcase}")
43
- @clazz.const_set("STATE_#{name.to_s.upcase}", name)
41
+ unless @klass.const_defined?("STATE_#{name.to_s.upcase}")
42
+ @klass.const_set("STATE_#{name.to_s.upcase}", name)
44
43
  end
45
44
  end
46
45
 
47
46
  # define an event
48
47
  def event(name, options={}, &block)
49
- # @clazz.aasm_event(name, options, &block)
50
-
51
48
  @state_machine.events[name] = AASM::Event.new(name, options, &block)
52
49
 
53
50
  # an addition over standard aasm so that, before firing an event, you can ask
54
51
  # may_event? and get back a boolean that tells you whether the guard method
55
52
  # on the transition will let this happen.
56
- @clazz.send(:define_method, "may_#{name.to_s}?") do |*args|
53
+ @klass.send(:define_method, "may_#{name.to_s}?") do |*args|
57
54
  aasm.may_fire_event?(name, *args)
58
55
  end
59
56
 
60
- @clazz.send(:define_method, "#{name.to_s}!") do |*args, &block|
57
+ @klass.send(:define_method, "#{name.to_s}!") do |*args, &block|
58
+ aasm.current_event = "#{name.to_s}!".to_sym
61
59
  aasm_fire_event(name, {:persist => true}, *args, &block)
62
60
  end
63
61
 
64
- @clazz.send(:define_method, "#{name.to_s}") do |*args, &block|
62
+ @klass.send(:define_method, "#{name.to_s}") do |*args, &block|
63
+ aasm.current_event = name.to_sym
65
64
  aasm_fire_event(name, {:persist => false}, *args, &block)
66
65
  end
67
66
  end
@@ -78,5 +77,24 @@ module AASM
78
77
  states.map { |state| state.for_select }
79
78
  end
80
79
 
80
+ def from_states_for_state(state, options={})
81
+ if options[:transition]
82
+ events[options[:transition]].transitions_to_state(state).flatten.map(&:from).flatten
83
+ else
84
+ events.map {|k,v| v.transitions_to_state(state)}.flatten.map(&:from).flatten
85
+ end
86
+ end
87
+
88
+ private
89
+
90
+ def configure(key, default_value)
91
+ @state_machine.config.send(:new_ostruct_member, key)
92
+ if @options.key?(key)
93
+ @state_machine.config.send("#{key}=", @options[key])
94
+ elsif @state_machine.config.send(key).nil?
95
+ @state_machine.config.send("#{key}=", default_value)
96
+ end
97
+ end
98
+
81
99
  end
82
100
  end
data/lib/aasm/event.rb CHANGED
@@ -6,6 +6,7 @@ module AASM
6
6
  def initialize(name, options = {}, &block)
7
7
  @name = name
8
8
  @transitions = []
9
+ @guards = Array(options[:guard] || options[:guards])
9
10
  update(options, &block)
10
11
  end
11
12
 
@@ -25,7 +26,7 @@ module AASM
25
26
  end
26
27
 
27
28
  def transitions_from_state(state)
28
- @transitions.select { |t| t.from == state }
29
+ @transitions.select { |t| t.from.nil? or t.from == state }
29
30
  end
30
31
 
31
32
  def transitions_to_state?(state)
@@ -36,9 +37,9 @@ module AASM
36
37
  @transitions.select { |t| t.to == state }
37
38
  end
38
39
 
39
- # deprecated
40
+ # TODO remove this method in v4.0.0
40
41
  def all_transitions
41
- # warn "Event#all_transitions is deprecated and will be removed in version 3.2.0; please use Event#transitions instead!"
42
+ warn "Event#all_transitions is deprecated and will be removed in version 4.0.0; please use Event#transitions instead!"
42
43
  transitions
43
44
  end
44
45
 
@@ -54,8 +55,31 @@ module AASM
54
55
  end
55
56
  end
56
57
 
58
+ ## DSL interface
59
+ def transitions(definitions=nil)
60
+ if definitions # define new transitions
61
+ # Create a separate transition for each from-state to the given state
62
+ Array(definitions[:from]).each do |s|
63
+ @transitions << AASM::Transition.new(attach_event_guards(definitions.merge(:from => s.to_sym)))
64
+ end
65
+ # Create a transition if :to is specified without :from (transitions from ANY state)
66
+ if @transitions.empty? && definitions[:to]
67
+ @transitions << AASM::Transition.new(attach_event_guards(definitions))
68
+ end
69
+ end
70
+ @transitions
71
+ end
72
+
57
73
  private
58
74
 
75
+ def attach_event_guards(definitions)
76
+ unless @guards.empty?
77
+ given_guards = Array(definitions.delete(:guard) || definitions.delete(:guards))
78
+ definitions[:guards] = given_guards + @guards
79
+ end
80
+ definitions
81
+ end
82
+
59
83
  def update(options = {}, &block)
60
84
  @options = options
61
85
  if block then
@@ -68,7 +92,7 @@ module AASM
68
92
  def _fire(obj, test, to_state=nil, *args)
69
93
  result = test ? false : nil
70
94
  if @transitions.map(&:from).any?
71
- transitions = @transitions.select { |t| t.from == obj.aasm_current_state }
95
+ transitions = @transitions.select { |t| t.from == obj.aasm.current_state }
72
96
  return result if transitions.size == 0
73
97
  else
74
98
  transitions = @transitions
@@ -106,19 +130,6 @@ module AASM
106
130
  end
107
131
  end
108
132
 
109
- ## DSL interface
110
- def transitions(trans_opts=nil)
111
- if trans_opts # define new transitions
112
- # Create a separate transition for each from state to the given state
113
- Array(trans_opts[:from]).each do |s|
114
- @transitions << AASM::Transition.new(trans_opts.merge({:from => s.to_sym}))
115
- end
116
- # Create a transition if to is specified without from (transitions from ANY state)
117
- @transitions << AASM::Transition.new(trans_opts) if @transitions.empty? && trans_opts[:to]
118
- end
119
- @transitions
120
- end
121
-
122
133
  [:after, :before, :error, :success].each do |callback_name|
123
134
  define_method callback_name do |*args, &block|
124
135
  options[callback_name] = Array(options[callback_name])
@@ -1,6 +1,8 @@
1
1
  module AASM
2
2
  class InstanceBase
3
3
 
4
+ attr_accessor :from_state, :to_state, :current_event
5
+
4
6
  def initialize(instance)
5
7
  @instance = instance
6
8
  end
@@ -15,7 +17,7 @@ module AASM
15
17
  end
16
18
 
17
19
  def enter_initial_state
18
- state_name = determine_state_name(@instance.class.aasm_initial_state)
20
+ state_name = determine_state_name(@instance.class.aasm.initial_state)
19
21
  state_object = state_object_for_name(state_name)
20
22
 
21
23
  state_object.fire_callbacks(:before_enter, @instance)
@@ -33,7 +35,7 @@ module AASM
33
35
  def states(options={})
34
36
  if options[:permissible]
35
37
  # ugliness level 1000
36
- transitions = @instance.class.aasm.events.values.map {|e| e.transitions_from_state(current_state) }
38
+ transitions = @instance.class.aasm.events.values_at(*permissible_events).compact.map {|e| e.transitions_from_state(current_state) }
37
39
  tos = transitions.map {|t| t[0] ? t[0].to : nil}.flatten.compact.map(&:to_sym).uniq
38
40
  @instance.class.aasm.states.select {|s| tos.include?(s.name.to_sym)}
39
41
  else
@@ -73,8 +75,11 @@ module AASM
73
75
  end
74
76
 
75
77
  def may_fire_event?(name, *args)
76
- event = @instance.class.aasm.events[name]
77
- event.may_fire?(@instance, *args)
78
+ if event = @instance.class.aasm.events[name]
79
+ event.may_fire?(@instance, *args)
80
+ else
81
+ false # unknown event
82
+ end
78
83
  end
79
84
 
80
85
  def set_current_state_with_persistence(state)
@@ -47,7 +47,7 @@ module AASM
47
47
 
48
48
  def ancestors_list(klass)
49
49
  klass.ancestors.select do |ancestor|
50
- ancestor.respond_to?(:model_name) unless ancestor == ActiveRecord::Base
50
+ ancestor.respond_to?(:model_name) unless ancestor.name == 'ActiveRecord::Base'
51
51
  end
52
52
  end
53
53
  end
@@ -36,6 +36,12 @@ module AASM
36
36
  else
37
37
  base.before_validation_on_create(:aasm_ensure_initial_state)
38
38
  end
39
+
40
+ # ensure initial aasm state even when validations are skipped
41
+ base.before_create(:aasm_ensure_initial_state)
42
+
43
+ # ensure state is in the list of states
44
+ base.validate :aasm_validate_states
39
45
  end
40
46
 
41
47
  module ClassMethods
@@ -71,18 +77,19 @@ module AASM
71
77
  # Writes <tt>state</tt> to the state column and persists it to the database
72
78
  #
73
79
  # foo = Foo.find(1)
74
- # foo.aasm_current_state # => :opened
80
+ # foo.aasm.current_state # => :opened
75
81
  # foo.close!
76
- # foo.aasm_current_state # => :closed
77
- # Foo.find(1).aasm_current_state # => :closed
82
+ # foo.aasm.current_state # => :closed
83
+ # Foo.find(1).aasm.current_state # => :closed
78
84
  #
79
85
  # NOTE: intended to be called from an event
80
86
  def aasm_write_state(state)
81
87
  old_value = read_attribute(self.class.aasm_column)
82
- write_attribute(self.class.aasm_column, state.to_s)
88
+ aasm_write_attribute state
83
89
 
84
- success = if AASM::StateMachine[self.class].config.skip_validation_on_save
85
- self.class.where(self.class.primary_key => self.id).update_all(self.class.aasm_column => state.to_s) == 1
90
+ success = if aasm_skipping_validations
91
+ value = aasm_raw_attribute_value state
92
+ self.class.where(self.class.primary_key => self.id).update_all(self.class.aasm_column => value) == 1
86
93
  else
87
94
  self.save
88
95
  end
@@ -97,20 +104,52 @@ module AASM
97
104
  # Writes <tt>state</tt> to the state column, but does not persist it to the database
98
105
  #
99
106
  # foo = Foo.find(1)
100
- # foo.aasm_current_state # => :opened
107
+ # foo.aasm.current_state # => :opened
101
108
  # foo.close
102
- # foo.aasm_current_state # => :closed
103
- # Foo.find(1).aasm_current_state # => :opened
109
+ # foo.aasm.current_state # => :closed
110
+ # Foo.find(1).aasm.current_state # => :opened
104
111
  # foo.save
105
- # foo.aasm_current_state # => :closed
106
- # Foo.find(1).aasm_current_state # => :closed
112
+ # foo.aasm.current_state # => :closed
113
+ # Foo.find(1).aasm.current_state # => :closed
107
114
  #
108
115
  # NOTE: intended to be called from an event
109
116
  def aasm_write_state_without_persistence(state)
110
- write_attribute(self.class.aasm_column, state.to_s)
117
+ aasm_write_attribute state
111
118
  end
112
119
 
113
120
  private
121
+ def aasm_enum
122
+ case AASM::StateMachine[self.class].config.enum
123
+ when false then nil
124
+ when true then aasm_guess_enum_method
125
+ when nil then aasm_guess_enum_method if aasm_column_looks_like_enum
126
+ else AASM::StateMachine[self.class].config.enum
127
+ end
128
+ end
129
+
130
+ def aasm_column_looks_like_enum
131
+ self.class.columns_hash[self.class.aasm_column.to_s].type == :integer
132
+ end
133
+
134
+ def aasm_guess_enum_method
135
+ self.class.aasm_column.to_s.pluralize.to_sym
136
+ end
137
+
138
+ def aasm_skipping_validations
139
+ AASM::StateMachine[self.class].config.skip_validation_on_save
140
+ end
141
+
142
+ def aasm_write_attribute(state)
143
+ write_attribute self.class.aasm_column, aasm_raw_attribute_value(state)
144
+ end
145
+
146
+ def aasm_raw_attribute_value(state)
147
+ if aasm_enum
148
+ value = self.class.send(aasm_enum)[state]
149
+ else
150
+ value = state.to_s
151
+ end
152
+ end
114
153
 
115
154
  # Ensures that if the aasm_state column is nil and the record is new
116
155
  # that the initial state gets populated before validation on create
@@ -132,17 +171,27 @@ module AASM
132
171
  end
133
172
 
134
173
  def aasm_fire_event(name, options, *args, &block)
135
- success = self.class.transaction(:requires_new => true) do
136
- super
137
- end
174
+ success = options[:persist] ? self.class.transaction(:requires_new => requires_new?) { super } : super
138
175
 
139
- if success
176
+ if success && options[:persist]
140
177
  new_state = aasm.state_object_for_name(aasm.current_state)
141
178
  new_state.fire_callbacks(:after_commit, self)
142
179
  end
143
180
 
144
181
  success
145
182
  end
183
+
184
+ def requires_new?
185
+ AASM::StateMachine[self.class].config.requires_new_transaction
186
+ end
187
+
188
+ def aasm_validate_states
189
+ unless AASM::StateMachine[self.class].config.skip_validation_on_save
190
+ if aasm.current_state && !aasm.states.include?(aasm.current_state)
191
+ self.errors.add(AASM::StateMachine[self.class].config.column , "is invalid")
192
+ end
193
+ end
194
+ end
146
195
  end # InstanceMethods
147
196
 
148
197
  end
@@ -35,7 +35,7 @@ module AASM
35
35
  def aasm_read_state
36
36
  state = send(self.class.aasm_column)
37
37
  if new_record?
38
- state.blank? ? aasm.determine_state_name(self.class.aasm_initial_state) : state.to_sym
38
+ state.blank? ? aasm.determine_state_name(self.class.aasm.initial_state) : state.to_sym
39
39
  else
40
40
  state.nil? ? nil : state.to_sym
41
41
  end
@@ -87,26 +87,22 @@ module AASM
87
87
  # make sure to create a (named) scope for each state
88
88
  def state_with_scope(name, *args)
89
89
  state_without_scope(name, *args)
90
- if AASM::StateMachine[@clazz].config.create_scopes && !@clazz.respond_to?(name)
91
- if @clazz.ancestors.map {|klass| klass.to_s}.include?("ActiveRecord::Base")
90
+ if AASM::StateMachine[@klass].config.create_scopes && !@klass.respond_to?(name)
91
+ if @klass.ancestors.map {|klass| klass.to_s}.include?("ActiveRecord::Base")
92
92
 
93
- conditions = {"#{@clazz.table_name}.#{@clazz.aasm_column}" => name.to_s}
94
- if ActiveRecord::VERSION::MAJOR >= 4
95
- @clazz.class_eval do
93
+ conditions = {"#{@klass.table_name}.#{@klass.aasm_column}" => name.to_s}
94
+ if ActiveRecord::VERSION::MAJOR >= 3
95
+ @klass.class_eval do
96
96
  scope name, lambda { where(conditions) }
97
97
  end
98
- elsif ActiveRecord::VERSION::MAJOR >= 3
99
- @clazz.class_eval do
100
- scope name, where(conditions)
101
- end
102
98
  else
103
- @clazz.class_eval do
99
+ @klass.class_eval do
104
100
  named_scope name, :conditions => conditions
105
101
  end
106
102
  end
107
- elsif @clazz.ancestors.map {|klass| klass.to_s}.include?("Mongoid::Document")
108
- scope_options = lambda { @clazz.send(:where, {@clazz.aasm_column.to_sym => name.to_s}) }
109
- @clazz.send(:scope, name, scope_options)
103
+ elsif @klass.ancestors.map {|klass| klass.to_s}.include?("Mongoid::Document")
104
+ scope_options = lambda { @klass.send(:where, {@klass.aasm_column.to_sym => name.to_s}) }
105
+ @klass.send(:scope, name, scope_options)
110
106
  end
111
107
  end
112
108
  end
@@ -36,6 +36,8 @@ module AASM
36
36
  # Mongoid's Validatable gem dependency goes not have a before_validation_on_xxx hook yet.
37
37
  # base.before_validation_on_create :aasm_ensure_initial_state
38
38
  base.before_validation :aasm_ensure_initial_state
39
+ # ensure initial aasm state even when validations are skipped
40
+ base.before_create :aasm_ensure_initial_state
39
41
  end
40
42
 
41
43
  module ClassMethods
@@ -66,10 +68,10 @@ module AASM
66
68
  # using update_attribute (which bypasses validation)
67
69
  #
68
70
  # foo = Foo.find(1)
69
- # foo.aasm_current_state # => :opened
71
+ # foo.aasm.current_state # => :opened
70
72
  # foo.close!
71
- # foo.aasm_current_state # => :closed
72
- # Foo.find(1).aasm_current_state # => :closed
73
+ # foo.aasm.current_state # => :closed
74
+ # Foo.find(1).aasm.current_state # => :closed
73
75
  #
74
76
  # NOTE: intended to be called from an event
75
77
  def aasm_write_state(state)
@@ -87,13 +89,13 @@ module AASM
87
89
  # Writes <tt>state</tt> to the state column, but does not persist it to the database
88
90
  #
89
91
  # foo = Foo.find(1)
90
- # foo.aasm_current_state # => :opened
92
+ # foo.aasm.current_state # => :opened
91
93
  # foo.close
92
- # foo.aasm_current_state # => :closed
93
- # Foo.find(1).aasm_current_state # => :opened
94
+ # foo.aasm.current_state # => :closed
95
+ # Foo.find(1).aasm.current_state # => :opened
94
96
  # foo.save
95
- # foo.aasm_current_state # => :closed
96
- # Foo.find(1).aasm_current_state # => :closed
97
+ # foo.aasm.current_state # => :closed
98
+ # Foo.find(1).aasm.current_state # => :closed
97
99
  #
98
100
  # NOTE: intended to be called from an event
99
101
  def aasm_write_state_without_persistence(state)