aasm 4.0.8 → 4.5.2

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 (147) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +7 -2
  3. data/CHANGELOG.md +43 -2
  4. data/CODE_OF_CONDUCT.md +13 -0
  5. data/Gemfile +6 -3
  6. data/LICENSE +1 -1
  7. data/PLANNED_CHANGES.md +8 -4
  8. data/README.md +171 -14
  9. data/aasm.gemspec +1 -5
  10. data/gemfiles/rails_3.2.gemfile +3 -2
  11. data/gemfiles/rails_4.0.gemfile +2 -6
  12. data/gemfiles/rails_4.0_mongo_mapper.gemfile +14 -0
  13. data/gemfiles/rails_4.1.gemfile +2 -6
  14. data/gemfiles/rails_4.1_mongo_mapper.gemfile +14 -0
  15. data/gemfiles/rails_4.2.gemfile +1 -5
  16. data/gemfiles/rails_4.2_mongo_mapper.gemfile +14 -0
  17. data/gemfiles/rails_4.2_mongoid_5.gemfile +12 -0
  18. data/lib/aasm/aasm.rb +69 -36
  19. data/lib/aasm/base.rb +51 -21
  20. data/lib/aasm/core/event.rb +7 -6
  21. data/lib/aasm/core/state.rb +11 -8
  22. data/lib/aasm/core/transition.rb +7 -5
  23. data/lib/aasm/errors.rb +15 -1
  24. data/lib/aasm/instance_base.rb +17 -13
  25. data/lib/aasm/localizer.rb +1 -1
  26. data/lib/aasm/persistence/active_record_persistence.rb +63 -73
  27. data/lib/aasm/persistence/base.rb +55 -20
  28. data/lib/aasm/persistence/mongo_mapper_persistence.rb +157 -0
  29. data/lib/aasm/persistence/mongoid_persistence.rb +16 -41
  30. data/lib/aasm/persistence/plain_persistence.rb +8 -7
  31. data/lib/aasm/persistence/sequel_persistence.rb +12 -9
  32. data/lib/aasm/persistence.rb +2 -0
  33. data/lib/aasm/rspec/allow_event.rb +22 -0
  34. data/lib/aasm/rspec/allow_transition_to.rb +22 -0
  35. data/lib/aasm/rspec/have_state.rb +22 -0
  36. data/lib/aasm/rspec/transition_from.rb +32 -0
  37. data/lib/aasm/rspec.rb +5 -0
  38. data/lib/aasm/state_machine.rb +20 -6
  39. data/lib/aasm/version.rb +1 -1
  40. data/spec/database.rb +27 -1
  41. data/spec/models/active_record/basic_active_record_two_state_machines_example.rb +25 -0
  42. data/spec/models/active_record/complex_active_record_example.rb +33 -0
  43. data/spec/models/active_record/derivate_new_dsl.rb +7 -0
  44. data/spec/models/active_record/false_state.rb +35 -0
  45. data/spec/models/active_record/gate.rb +39 -0
  46. data/spec/models/active_record/localizer_test_model.rb +34 -0
  47. data/spec/models/active_record/no_direct_assignment.rb +21 -0
  48. data/spec/models/active_record/no_scope.rb +21 -0
  49. data/spec/models/active_record/persisted_state.rb +12 -0
  50. data/spec/models/active_record/provided_and_persisted_state.rb +24 -0
  51. data/spec/models/active_record/reader.rb +7 -0
  52. data/spec/models/active_record/readme_job.rb +21 -0
  53. data/spec/models/active_record/simple_new_dsl.rb +17 -0
  54. data/spec/models/active_record/thief.rb +29 -0
  55. data/spec/models/active_record/transient.rb +6 -0
  56. data/spec/models/active_record/with_enum.rb +39 -0
  57. data/spec/models/active_record/with_false_enum.rb +31 -0
  58. data/spec/models/active_record/with_true_enum.rb +39 -0
  59. data/spec/models/active_record/writer.rb +6 -0
  60. data/spec/models/basic_two_state_machines_example.rb +25 -0
  61. data/spec/models/callbacks/basic.rb +3 -0
  62. data/spec/models/callbacks/basic_multiple.rb +75 -0
  63. data/spec/models/callbacks/guard_within_block_multiple.rb +66 -0
  64. data/spec/models/callbacks/multiple_transitions_transition_guard_multiple.rb +65 -0
  65. data/spec/models/callbacks/private_method_multiple.rb +44 -0
  66. data/spec/models/callbacks/with_args.rb +9 -9
  67. data/spec/models/callbacks/with_args_multiple.rb +61 -0
  68. data/spec/models/callbacks/{with_state_args.rb → with_state_arg.rb} +1 -1
  69. data/spec/models/callbacks/with_state_arg_multiple.rb +26 -0
  70. data/spec/models/complex_example.rb +222 -0
  71. data/spec/models/conversation.rb +47 -1
  72. data/spec/models/default_state.rb +12 -0
  73. data/spec/models/foo.rb +57 -0
  74. data/spec/models/foo_callback_multiple.rb +45 -0
  75. data/spec/models/guardian.rb +10 -0
  76. data/spec/models/guardian_multiple.rb +48 -0
  77. data/spec/models/initial_state_proc.rb +31 -0
  78. data/spec/models/invalid_persistor.rb +15 -0
  79. data/spec/models/mongo_mapper/complex_mongo_mapper_example.rb +37 -0
  80. data/spec/models/mongo_mapper/no_scope_mongo_mapper.rb +21 -0
  81. data/spec/models/mongo_mapper/simple_mongo_mapper.rb +23 -0
  82. data/spec/models/mongo_mapper/simple_new_dsl_mongo_mapper.rb +25 -0
  83. data/spec/models/mongoid/complex_mongoid_example.rb +37 -0
  84. data/spec/models/mongoid/no_scope_mongoid.rb +11 -0
  85. data/spec/models/mongoid/simple_mongoid.rb +12 -0
  86. data/spec/models/mongoid/simple_new_dsl_mongoid.rb +14 -1
  87. data/spec/models/no_initial_state.rb +25 -0
  88. data/spec/models/parametrised_event.rb +1 -1
  89. data/spec/models/parametrised_event_multiple.rb +29 -0
  90. data/spec/models/provided_state.rb +24 -0
  91. data/spec/models/sequel/complex_sequel_example.rb +45 -0
  92. data/spec/models/sequel/sequel_multiple.rb +25 -0
  93. data/spec/models/sequel/sequel_simple.rb +25 -0
  94. data/spec/models/silencer.rb +5 -0
  95. data/spec/models/simple_example.rb +15 -0
  96. data/spec/models/simple_multiple_example.rb +30 -0
  97. data/spec/models/state_machine_with_failed_event.rb +12 -0
  98. data/spec/models/sub_class.rb +7 -0
  99. data/spec/models/sub_class_with_more_states.rb +18 -0
  100. data/spec/models/super_class.rb +46 -0
  101. data/spec/models/transactor.rb +27 -0
  102. data/spec/models/valid_state_name.rb +23 -0
  103. data/spec/models/validator.rb +47 -0
  104. data/spec/spec_helper.rb +4 -4
  105. data/spec/unit/api_spec.rb +6 -1
  106. data/spec/unit/basic_two_state_machines_example_spec.rb +10 -0
  107. data/spec/unit/callback_multiple_spec.rb +295 -0
  108. data/spec/unit/callbacks_spec.rb +3 -2
  109. data/spec/unit/complex_example_spec.rb +2 -2
  110. data/spec/unit/complex_multiple_example_spec.rb +99 -0
  111. data/spec/unit/edge_cases_spec.rb +16 -0
  112. data/spec/unit/event_multiple_spec.rb +73 -0
  113. data/spec/unit/event_naming_spec.rb +2 -15
  114. data/spec/unit/event_spec.rb +11 -6
  115. data/spec/unit/guard_multiple_spec.rb +60 -0
  116. data/spec/unit/guard_spec.rb +12 -0
  117. data/spec/unit/initial_state_multiple_spec.rb +15 -0
  118. data/spec/unit/initial_state_spec.rb +3 -18
  119. data/spec/unit/inspection_multiple_spec.rb +201 -0
  120. data/spec/unit/inspection_spec.rb +12 -7
  121. data/spec/unit/localizer_spec.rb +0 -38
  122. data/spec/unit/persistence/active_record_persistence_multiple_spec.rb +573 -0
  123. data/spec/unit/persistence/active_record_persistence_spec.rb +70 -13
  124. data/spec/unit/persistence/mongo_mapper_persistence_multiple_spec.rb +146 -0
  125. data/spec/unit/persistence/mongo_mapper_persistence_spec.rb +93 -0
  126. data/spec/unit/persistence/mongoid_persistence_multiple_spec.rb +127 -0
  127. data/spec/unit/persistence/mongoid_persistence_spec.rb +79 -0
  128. data/spec/unit/persistence/sequel_persistence_multiple_spec.rb +153 -0
  129. data/spec/unit/persistence/sequel_persistence_spec.rb +20 -23
  130. data/spec/unit/readme_spec.rb +42 -0
  131. data/spec/unit/reloading_spec.rb +1 -1
  132. data/spec/unit/rspec_matcher_spec.rb +79 -0
  133. data/spec/unit/simple_example_spec.rb +26 -42
  134. data/spec/unit/simple_multiple_example_spec.rb +63 -0
  135. data/spec/unit/state_spec.rb +3 -1
  136. data/spec/unit/subclassing_multiple_spec.rb +39 -0
  137. data/spec/unit/subclassing_spec.rb +10 -10
  138. data/spec/unit/transition_spec.rb +39 -25
  139. metadata +152 -23
  140. data/spec/models/active_record/api.rb +0 -75
  141. data/spec/models/argument.rb +0 -11
  142. data/spec/models/auth_machine.rb +0 -88
  143. data/spec/models/bar.rb +0 -15
  144. data/spec/models/father.rb +0 -21
  145. data/spec/models/persistence.rb +0 -164
  146. data/spec/models/son.rb +0 -3
  147. data/spec/unit/persistence/mongoid_persistance_spec.rb +0 -152
data/lib/aasm/aasm.rb CHANGED
@@ -8,36 +8,63 @@ module AASM
8
8
 
9
9
  # do not overwrite existing state machines, which could have been created by
10
10
  # inheritance, see class method inherited
11
- AASM::StateMachine[base] ||= AASM::StateMachine.new
11
+ AASM::StateMachine[base] ||= {}
12
12
 
13
13
  AASM::Persistence.load_persistence(base)
14
14
  super
15
15
  end
16
16
 
17
17
  module ClassMethods
18
-
19
18
  # make sure inheritance (aka subclassing) works with AASM
20
19
  def inherited(base)
21
- AASM::StateMachine[base] = AASM::StateMachine[self].clone
20
+ AASM::StateMachine[base] = {}
21
+ AASM::StateMachine[self].keys.each do |state_machine_name|
22
+ AASM::StateMachine[base][state_machine_name] = AASM::StateMachine[self][state_machine_name].clone
23
+ end
22
24
  super
23
25
  end
24
26
 
25
27
  # this is the entry point for all state and event definitions
26
- def aasm(options={}, &block)
27
- @aasm ||= AASM::Base.new(self, options)
28
- @aasm.instance_eval(&block) if block # new DSL
29
- @aasm
30
- end
28
+ def aasm(*args, &block)
29
+ if args[0].is_a?(Symbol) || args[0].is_a?(String)
30
+ # using custom name
31
+ state_machine_name = args[0].to_sym
32
+ options = args[1] || {}
33
+ else
34
+ # using the default state_machine_name
35
+ state_machine_name = :default
36
+ options = args[0] || {}
37
+ end
31
38
 
32
- # deprecated, remove in version 4.1
33
- def aasm_human_event_name(event) # event_name?
34
- warn '[DEPRECATION] AASM: aasm_human_event_name is deprecated, use aasm.human_event_name instead'
35
- aasm.human_event_name(event)
39
+ AASM::StateMachine[self][state_machine_name] ||= AASM::StateMachine.new(state_machine_name)
40
+
41
+ @aasm ||= {}
42
+ if @aasm[state_machine_name]
43
+ # make sure to use provided options
44
+ options.each do |key, value|
45
+ @aasm[state_machine_name].state_machine.config.send("#{key}=", value)
46
+ end
47
+ else
48
+ # create a new base
49
+ @aasm[state_machine_name] = AASM::Base.new(
50
+ self,
51
+ state_machine_name,
52
+ AASM::StateMachine[self][state_machine_name],
53
+ options
54
+ )
55
+ end
56
+ @aasm[state_machine_name].instance_eval(&block) if block # new DSL
57
+ @aasm[state_machine_name]
36
58
  end
37
59
  end # ClassMethods
38
60
 
39
- def aasm
40
- @aasm ||= AASM::InstanceBase.new(self)
61
+ # this is the entry point for all instance-level access to AASM
62
+ def aasm(name=:default)
63
+ unless AASM::StateMachine[self.class][name.to_sym]
64
+ raise AASM::UnknownStateMachineError.new("There is no state machine with the name '#{name}' defined in #{self.class.name}!")
65
+ end
66
+ @aasm ||= {}
67
+ @aasm[name.to_sym] ||= AASM::InstanceBase.new(self, name.to_sym)
41
68
  end
42
69
 
43
70
  private
@@ -56,66 +83,72 @@ private
56
83
  return args
57
84
  end
58
85
 
59
- def aasm_fire_event(event_name, options, *args, &block)
60
- event = self.class.aasm.state_machine.events[event_name]
86
+ def aasm_fire_event(state_machine_name, event_name, options, *args, &block)
87
+ event = self.class.aasm(state_machine_name).state_machine.events[event_name]
61
88
  begin
62
- old_state = aasm.state_object_for_name(aasm.current_state)
89
+ old_state = aasm(state_machine_name).state_object_for_name(aasm(state_machine_name).current_state)
63
90
 
64
91
  # new event before callback
65
92
  event.fire_callbacks(
66
93
  :before,
67
94
  self,
68
- *process_args(event, aasm.current_state, *args)
95
+ *process_args(event, aasm(state_machine_name).current_state, *args)
69
96
  )
70
97
 
71
98
  if may_fire_to = event.may_fire?(self, *args)
72
- old_state.fire_callbacks(:before_exit, self)
73
- old_state.fire_callbacks(:exit, self) # TODO: remove for AASM 4?
99
+ old_state.fire_callbacks(:before_exit, self,
100
+ *process_args(event, aasm(state_machine_name).current_state, *args))
101
+ old_state.fire_callbacks(:exit, self,
102
+ *process_args(event, aasm(state_machine_name).current_state, *args))
74
103
 
75
104
  if new_state_name = event.fire(self, {:may_fire => may_fire_to}, *args)
76
- aasm_fired(event, old_state, new_state_name, options, *args, &block)
105
+ aasm_fired(state_machine_name, event, old_state, new_state_name, options, *args, &block)
77
106
  else
78
- aasm_failed(event_name, old_state)
107
+ aasm_failed(state_machine_name, event_name, old_state)
79
108
  end
80
109
  else
81
- aasm_failed(event_name, old_state)
110
+ aasm_failed(state_machine_name, event_name, old_state)
82
111
  end
83
112
  rescue StandardError => e
84
- event.fire_callbacks(:error, self, e, *process_args(event, aasm.current_state, *args)) || raise(e)
113
+ event.fire_callbacks(:error, self, e, *process_args(event, aasm(state_machine_name).current_state, *args)) || raise(e)
85
114
  end
86
115
  end
87
116
 
88
- def aasm_fired(event, old_state, new_state_name, options, *args)
117
+ def aasm_fired(state_machine_name, event, old_state, new_state_name, options, *args)
89
118
  persist = options[:persist]
90
119
 
91
- new_state = aasm.state_object_for_name(new_state_name)
120
+ new_state = aasm(state_machine_name).state_object_for_name(new_state_name)
92
121
 
93
- new_state.fire_callbacks(:before_enter, self)
122
+ new_state.fire_callbacks(:before_enter, self,
123
+ *process_args(event, aasm(state_machine_name).current_state, *args))
94
124
 
95
- new_state.fire_callbacks(:enter, self) # TODO: remove for AASM 4?
125
+ new_state.fire_callbacks(:enter, self,
126
+ *process_args(event, aasm(state_machine_name).current_state, *args)) # TODO: remove for AASM 4?
96
127
 
97
128
  persist_successful = true
98
129
  if persist
99
- persist_successful = aasm.set_current_state_with_persistence(new_state_name)
130
+ persist_successful = aasm(state_machine_name).set_current_state_with_persistence(new_state_name)
100
131
  if persist_successful
101
132
  yield if block_given?
102
133
  event.fire_callbacks(:success, self)
103
134
  end
104
135
  else
105
- aasm.current_state = new_state_name
136
+ aasm(state_machine_name).current_state = new_state_name
106
137
  yield if block_given?
107
138
  end
108
139
 
109
140
  if persist_successful
110
- old_state.fire_callbacks(:after_exit, self)
111
- new_state.fire_callbacks(:after_enter, self)
141
+ old_state.fire_callbacks(:after_exit, self,
142
+ *process_args(event, aasm(state_machine_name).current_state, *args))
143
+ new_state.fire_callbacks(:after_enter, self,
144
+ *process_args(event, aasm(state_machine_name).current_state, *args))
112
145
  event.fire_callbacks(
113
146
  :after,
114
147
  self,
115
148
  *process_args(event, old_state.name, *args)
116
149
  )
117
150
 
118
- self.aasm_event_fired(event.name, old_state.name, aasm.current_state) if self.respond_to?(:aasm_event_fired)
151
+ self.aasm_event_fired(event.name, old_state.name, aasm(state_machine_name).current_state) if self.respond_to?(:aasm_event_fired)
119
152
  else
120
153
  self.aasm_event_failed(event.name, old_state.name) if self.respond_to?(:aasm_event_failed)
121
154
  end
@@ -123,13 +156,13 @@ private
123
156
  persist_successful
124
157
  end
125
158
 
126
- def aasm_failed(event_name, old_state)
159
+ def aasm_failed(state_machine_name, event_name, old_state)
127
160
  if self.respond_to?(:aasm_event_failed)
128
161
  self.aasm_event_failed(event_name, old_state.name)
129
162
  end
130
163
 
131
- if AASM::StateMachine[self.class].config.whiny_transitions
132
- raise AASM::InvalidTransition, "Event '#{event_name}' cannot transition from '#{aasm.current_state}'"
164
+ if AASM::StateMachine[self.class][state_machine_name].config.whiny_transitions
165
+ raise AASM::InvalidTransition.new(self, event_name, state_machine_name)
133
166
  else
134
167
  false
135
168
  end
data/lib/aasm/base.rb CHANGED
@@ -3,10 +3,12 @@ module AASM
3
3
 
4
4
  attr_reader :state_machine
5
5
 
6
- def initialize(klass, options={}, &block)
6
+ def initialize(klass, name, state_machine, options={}, &block)
7
7
  @klass = klass
8
- @state_machine = AASM::StateMachine[@klass]
9
- @state_machine.config.column ||= (options[:column] || :aasm_state).to_sym # aasm4
8
+ @name = name
9
+ # @state_machine = @klass.aasm(@name).state_machine
10
+ @state_machine = state_machine
11
+ @state_machine.config.column ||= (options[:column] || default_column).to_sym
10
12
  # @state_machine.config.column = options[:column].to_sym if options[:column] # master
11
13
  @options = options
12
14
 
@@ -27,11 +29,19 @@ module AASM
27
29
 
28
30
  configure :enum, nil
29
31
 
30
- if @state_machine.config.no_direct_assignment
31
- @klass.send(:define_method, "#{@state_machine.config.column}=") do |state_name|
32
- raise AASM::NoDirectAssignmentError.new('direct assignment of AASM column has been disabled (see AASM configuration for this class)')
32
+ # make sure to raise an error if no_direct_assignment is enabled
33
+ # and attribute is directly assigned though
34
+ @klass.class_eval %Q(
35
+ def #{@state_machine.config.column}=(state_name)
36
+ if self.class.aasm(:#{@name}).state_machine.config.no_direct_assignment
37
+ raise AASM::NoDirectAssignmentError.new(
38
+ 'direct assignment of AASM column has been disabled (see AASM configuration for this class)'
39
+ )
40
+ else
41
+ super
42
+ end
33
43
  end
34
- end
44
+ )
35
45
  end
36
46
 
37
47
  # This method is both a getter and a setter
@@ -56,10 +66,16 @@ module AASM
56
66
  def state(name, options={})
57
67
  @state_machine.add_state(name, @klass, options)
58
68
 
59
- @klass.send(:define_method, "#{name}?") do
60
- aasm.current_state == name
69
+ if @klass.instance_methods.include?("#{name}?")
70
+ warn "#{@klass.name}: The aasm state name #{name} is already used!"
61
71
  end
62
72
 
73
+ @klass.class_eval <<-EORUBY, __FILE__, __LINE__ + 1
74
+ def #{name}?
75
+ aasm(:#{@name}).current_state == :#{name}
76
+ end
77
+ EORUBY
78
+
63
79
  unless @klass.const_defined?("STATE_#{name.upcase}")
64
80
  @klass.const_set("STATE_#{name.upcase}", name)
65
81
  end
@@ -67,24 +83,34 @@ module AASM
67
83
 
68
84
  # define an event
69
85
  def event(name, options={}, &block)
70
- @state_machine.events[name] = AASM::Core::Event.new(name, options, &block)
86
+ @state_machine.add_event(name, options, &block)
87
+
88
+ if @klass.instance_methods.include?("may_#{name}?".to_sym)
89
+ warn "#{@klass.name}: The aasm event name #{name} is already used!"
90
+ end
71
91
 
72
92
  # an addition over standard aasm so that, before firing an event, you can ask
73
93
  # may_event? and get back a boolean that tells you whether the guard method
74
94
  # on the transition will let this happen.
75
- @klass.send(:define_method, "may_#{name}?") do |*args|
76
- aasm.may_fire_event?(name, *args)
77
- end
95
+ @klass.class_eval <<-EORUBY, __FILE__, __LINE__ + 1
96
+ def may_#{name}?(*args)
97
+ aasm(:#{@name}).may_fire_event?(:#{name}, *args)
98
+ end
78
99
 
79
- @klass.send(:define_method, "#{name}!") do |*args, &block|
80
- aasm.current_event = "#{name}!".to_sym
81
- aasm_fire_event(name, {:persist => true}, *args, &block)
82
- end
100
+ def #{name}!(*args, &block)
101
+ aasm(:#{@name}).current_event = :#{name}!
102
+ aasm_fire_event(:#{@name}, :#{name}, {:persist => true}, *args, &block)
103
+ end
83
104
 
84
- @klass.send(:define_method, "#{name}") do |*args, &block|
85
- aasm.current_event = name.to_sym
86
- aasm_fire_event(name, {:persist => false}, *args, &block)
87
- end
105
+ def #{name}(*args, &block)
106
+ aasm(:#{@name}).current_event = :#{name}
107
+ aasm_fire_event(:#{@name}, :#{name}, {:persist => false}, *args, &block)
108
+ end
109
+ EORUBY
110
+ end
111
+
112
+ def after_all_transitions(*callbacks, &block)
113
+ @state_machine.add_global_callbacks(:after_all_transitions, *callbacks, &block)
88
114
  end
89
115
 
90
116
  def states
@@ -114,6 +140,10 @@ module AASM
114
140
 
115
141
  private
116
142
 
143
+ def default_column
144
+ @name.to_sym == :default ? :aasm_state : @name.to_sym
145
+ end
146
+
117
147
  def configure(key, default_value)
118
148
  if @options.key?(key)
119
149
  @state_machine.config.send("#{key}=", @options[key])
@@ -2,17 +2,18 @@ module AASM::Core
2
2
  class Event
3
3
  include DslHelper
4
4
 
5
- attr_reader :name, :options
5
+ attr_reader :name, :state_machine, :options
6
6
 
7
- def initialize(name, options = {}, &block)
7
+ def initialize(name, state_machine, options = {}, &block)
8
8
  @name = name
9
+ @state_machine = state_machine
9
10
  @transitions = []
10
11
  @guards = Array(options[:guard] || options[:guards] || options[:if])
11
12
  @unless = Array(options[:unless]) #TODO: This could use a better name
12
13
 
13
14
  # from aasm4
14
15
  @options = options # QUESTION: .dup ?
15
- add_options_from_dsl(@options, [:after, :before, :error, :success], &block) if block
16
+ add_options_from_dsl(@options, [:after, :before, :error, :success, :after_commit], &block) if block
16
17
  end
17
18
 
18
19
  # a neutered version of fire - it doesn't actually fire the event, it just
@@ -61,11 +62,11 @@ module AASM::Core
61
62
  if definitions # define new transitions
62
63
  # Create a separate transition for each from-state to the given state
63
64
  Array(definitions[:from]).each do |s|
64
- @transitions << AASM::Core::Transition.new(attach_event_guards(definitions.merge(:from => s.to_sym)), &block)
65
+ @transitions << AASM::Core::Transition.new(self, attach_event_guards(definitions.merge(:from => s.to_sym)), &block)
65
66
  end
66
67
  # Create a transition if :to is specified without :from (transitions from ANY state)
67
68
  if @transitions.empty? && definitions[:to]
68
- @transitions << AASM::Core::Transition.new(attach_event_guards(definitions), &block)
69
+ @transitions << AASM::Core::Transition.new(self, attach_event_guards(definitions), &block)
69
70
  end
70
71
  end
71
72
  @transitions
@@ -89,7 +90,7 @@ module AASM::Core
89
90
  def _fire(obj, options={}, to_state=nil, *args)
90
91
  result = options[:test_only] ? false : nil
91
92
  if @transitions.map(&:from).any?
92
- transitions = @transitions.select { |t| t.from == obj.aasm.current_state }
93
+ transitions = @transitions.select { |t| t.from == obj.aasm(state_machine.name).current_state }
93
94
  return result if transitions.size == 0
94
95
  else
95
96
  transitions = @transitions
@@ -1,10 +1,11 @@
1
1
  module AASM::Core
2
2
  class State
3
- attr_reader :name, :options
3
+ attr_reader :name, :state_machine, :options
4
4
 
5
- def initialize(name, klass, options={})
5
+ def initialize(name, klass, state_machine, options={})
6
6
  @name = name
7
7
  @klass = klass
8
+ @state_machine = state_machine
8
9
  update(options)
9
10
  end
10
11
 
@@ -28,12 +29,12 @@ module AASM::Core
28
29
  name.to_s
29
30
  end
30
31
 
31
- def fire_callbacks(action, record)
32
+ def fire_callbacks(action, record, *args)
32
33
  action = @options[action]
33
34
  catch :halt_aasm_chain do
34
35
  action.is_a?(Array) ?
35
- action.each {|a| _fire_callbacks(a, record)} :
36
- _fire_callbacks(action, record)
36
+ action.each {|a| _fire_callbacks(a, record, args)} :
37
+ _fire_callbacks(action, record, args)
37
38
  end
38
39
  end
39
40
 
@@ -66,12 +67,14 @@ module AASM::Core
66
67
  self
67
68
  end
68
69
 
69
- def _fire_callbacks(action, record)
70
+ def _fire_callbacks(action, record, args)
70
71
  case action
71
72
  when Symbol, String
72
- record.send(action)
73
+ arity = record.send(:method, action.to_sym).arity
74
+ record.send(action, *(arity < 0 ? args : args[0...arity]))
73
75
  when Proc
74
- action.call(record)
76
+ arity = action.arity
77
+ action.call(record, *(arity < 0 ? args : args[0...arity]))
75
78
  end
76
79
  end
77
80
 
@@ -2,12 +2,13 @@ module AASM::Core
2
2
  class Transition
3
3
  include DslHelper
4
4
 
5
- attr_reader :from, :to, :opts
5
+ attr_reader :from, :to, :event, :opts
6
6
  alias_method :options, :opts
7
7
 
8
- def initialize(opts, &block)
8
+ def initialize(event, opts, &block)
9
9
  add_options_from_dsl(opts, [:on_transition, :guard, :after], &block) if block
10
10
 
11
+ @event = event
11
12
  @from = opts[:from]
12
13
  @to = opts[:to]
13
14
  @guards = Array(opts[:guards]) + Array(opts[:guard]) + Array(opts[:if])
@@ -29,6 +30,7 @@ module AASM::Core
29
30
  end
30
31
 
31
32
  def execute(obj, *args)
33
+ invoke_callbacks_compatible_with_guard(event.state_machine.global_callbacks[:after_all_transitions], obj, args)
32
34
  invoke_callbacks_compatible_with_guard(@after, obj, args)
33
35
  end
34
36
 
@@ -44,8 +46,8 @@ module AASM::Core
44
46
 
45
47
  def invoke_callbacks_compatible_with_guard(code, record, args, options={})
46
48
  if record.respond_to?(:aasm)
47
- record.aasm.from_state = @from if record.aasm.respond_to?(:from_state=)
48
- record.aasm.to_state = @to if record.aasm.respond_to?(:to_state=)
49
+ record.aasm(event.state_machine.name).from_state = @from if record.aasm(event.state_machine.name).respond_to?(:from_state=)
50
+ record.aasm(event.state_machine.name).to_state = @to if record.aasm(event.state_machine.name).respond_to?(:to_state=)
49
51
  end
50
52
 
51
53
  case code
@@ -53,7 +55,7 @@ module AASM::Core
53
55
  arity = record.send(:method, code.to_sym).arity
54
56
  arity == 0 ? record.send(code) : record.send(code, *args)
55
57
  when Proc
56
- code.arity == 0 ? record.instance_exec(&code) : record.instance_exec(*args, &code)
58
+ code.parameters.size == 0 ? record.instance_exec(&code) : record.instance_exec(*args, &code)
57
59
  when Array
58
60
  if options[:guard]
59
61
  # invoke guard callbacks
data/lib/aasm/errors.rb CHANGED
@@ -1,5 +1,19 @@
1
1
  module AASM
2
- class InvalidTransition < RuntimeError; end
2
+
3
+ class UnknownStateMachineError < RuntimeError; end
4
+
5
+ class InvalidTransition < RuntimeError
6
+ attr_reader :object, :event_name, :state_machine_name
7
+
8
+ def initialize(object, event_name, state_machine_name)
9
+ @object, @event_name, @state_machine_name = object, event_name, state_machine_name
10
+ end
11
+
12
+ def message
13
+ "Event '#{event_name}' cannot transition from '#{object.aasm(state_machine_name).current_state}'"
14
+ end
15
+ end
16
+
3
17
  class UndefinedState < RuntimeError; end
4
18
  class NoDirectAssignmentError < RuntimeError; end
5
19
  end
@@ -3,21 +3,22 @@ module AASM
3
3
 
4
4
  attr_accessor :from_state, :to_state, :current_event
5
5
 
6
- def initialize(instance)
6
+ def initialize(instance, name=:default) # instance of the class including AASM, name of the state machine
7
7
  @instance = instance
8
+ @name = name
8
9
  end
9
10
 
10
11
  def current_state
11
- @instance.aasm_read_state
12
+ @instance.aasm_read_state(@name)
12
13
  end
13
14
 
14
15
  def current_state=(state)
15
- @instance.aasm_write_state_without_persistence(state)
16
- @current_state = state
16
+ @instance.aasm_write_state_without_persistence(state, @name)
17
+ # @current_state = state
17
18
  end
18
19
 
19
20
  def enter_initial_state
20
- state_name = determine_state_name(@instance.class.aasm.initial_state)
21
+ state_name = determine_state_name(@instance.class.aasm(@name).initial_state)
21
22
  state_object = state_object_for_name(state_name)
22
23
 
23
24
  state_object.fire_callbacks(:before_enter, @instance)
@@ -29,24 +30,27 @@ module AASM
29
30
  end
30
31
 
31
32
  def human_state
32
- AASM::Localizer.new.human_state_name(@instance.class, current_state)
33
+ AASM::Localizer.new.human_state_name(@instance.class, state_object_for_name(current_state))
33
34
  end
34
35
 
35
36
  def states(options={})
36
37
  if options[:permitted]
37
38
  # ugliness level 1000
38
39
  permitted_event_names = events(:permitted => true).map(&:name)
39
- transitions = @instance.class.aasm.state_machine.events.values_at(*permitted_event_names).compact.map {|e| e.transitions_from_state(current_state) }
40
+ transitions = @instance.class.aasm(@name).state_machine.events.values_at(*permitted_event_names).compact.map {|e| e.transitions_from_state(current_state) }
40
41
  tos = transitions.map {|t| t[0] ? t[0].to : nil}.flatten.compact.map(&:to_sym).uniq
41
- @instance.class.aasm.states.select {|s| tos.include?(s.name.to_sym)}
42
+ @instance.class.aasm(@name).states.select {|s| tos.include?(s.name.to_sym)}
42
43
  else
43
- @instance.class.aasm.states
44
+ @instance.class.aasm(@name).states
44
45
  end
45
46
  end
46
47
 
47
48
  def events(options={})
48
49
  state = options[:state] || current_state
49
- events = @instance.class.aasm.events.select {|e| e.transitions_from_state?(state) }
50
+ events = @instance.class.aasm(@name).events.select {|e| e.transitions_from_state?(state) }
51
+
52
+ options[:reject] = Array(options[:reject])
53
+ events.reject! { |e| options[:reject].include?(e.name) }
50
54
 
51
55
  if options[:permitted]
52
56
  # filters the results of events_for_current_state so that only those that
@@ -58,7 +62,7 @@ module AASM
58
62
  end
59
63
 
60
64
  def state_object_for_name(name)
61
- obj = @instance.class.aasm.states.find {|s| s == name}
65
+ obj = @instance.class.aasm(@name).states.find {|s| s.name == name}
62
66
  raise AASM::UndefinedState, "State :#{name} doesn't exist" if obj.nil?
63
67
  obj
64
68
  end
@@ -75,7 +79,7 @@ module AASM
75
79
  end
76
80
 
77
81
  def may_fire_event?(name, *args)
78
- if event = @instance.class.aasm.state_machine.events[name]
82
+ if event = @instance.class.aasm(@name).state_machine.events[name]
79
83
  !!event.may_fire?(@instance, *args)
80
84
  else
81
85
  false # unknown event
@@ -83,7 +87,7 @@ module AASM
83
87
  end
84
88
 
85
89
  def set_current_state_with_persistence(state)
86
- save_success = @instance.aasm_write_state(state)
90
+ save_success = @instance.aasm_write_state(state, @name)
87
91
  self.current_state = state if save_success
88
92
  save_success
89
93
  end
@@ -21,7 +21,7 @@ module AASM
21
21
 
22
22
  def item_for(klass, state, ancestor, options={})
23
23
  separator = options[:old_style] ? '.' : '/'
24
- :"#{i18n_scope(klass)}.attributes.#{i18n_klass(ancestor)}.#{klass.aasm.attribute_name}#{separator}#{state}"
24
+ :"#{i18n_scope(klass)}.attributes.#{i18n_klass(ancestor)}.#{klass.aasm(state.state_machine.name).attribute_name}#{separator}#{state}"
25
25
  end
26
26
 
27
27
  def translate_queue(checklist)