aasm 4.5.1 → 5.5.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 (186) hide show
  1. checksums.yaml +5 -5
  2. data/LICENSE +1 -1
  3. data/README.md +809 -129
  4. data/lib/aasm/aasm.rb +74 -37
  5. data/lib/aasm/base.rb +188 -41
  6. data/lib/aasm/configuration.rb +27 -2
  7. data/lib/aasm/core/event.rb +75 -47
  8. data/lib/aasm/core/invoker.rb +129 -0
  9. data/lib/aasm/core/invokers/base_invoker.rb +75 -0
  10. data/lib/aasm/core/invokers/class_invoker.rb +52 -0
  11. data/lib/aasm/core/invokers/literal_invoker.rb +49 -0
  12. data/lib/aasm/core/invokers/proc_invoker.rb +59 -0
  13. data/lib/aasm/core/state.rb +22 -13
  14. data/lib/aasm/core/transition.rb +30 -23
  15. data/lib/aasm/dsl_helper.rb +24 -22
  16. data/lib/aasm/errors.rb +8 -5
  17. data/lib/aasm/instance_base.rb +63 -15
  18. data/lib/aasm/localizer.rb +13 -3
  19. data/lib/aasm/minitest/allow_event.rb +13 -0
  20. data/lib/aasm/minitest/allow_transition_to.rb +13 -0
  21. data/lib/aasm/minitest/have_state.rb +13 -0
  22. data/lib/aasm/minitest/transition_from.rb +21 -0
  23. data/lib/aasm/minitest.rb +5 -0
  24. data/lib/aasm/minitest_spec.rb +15 -0
  25. data/lib/aasm/persistence/active_record_persistence.rb +87 -79
  26. data/lib/aasm/persistence/base.rb +30 -30
  27. data/lib/aasm/persistence/core_data_query_persistence.rb +94 -0
  28. data/lib/aasm/persistence/dynamoid_persistence.rb +92 -0
  29. data/lib/aasm/persistence/mongoid_persistence.rb +49 -35
  30. data/lib/aasm/persistence/no_brainer_persistence.rb +105 -0
  31. data/lib/aasm/persistence/orm.rb +154 -0
  32. data/lib/aasm/persistence/plain_persistence.rb +2 -1
  33. data/lib/aasm/persistence/redis_persistence.rb +112 -0
  34. data/lib/aasm/persistence/sequel_persistence.rb +37 -67
  35. data/lib/aasm/persistence.rb +20 -5
  36. data/lib/aasm/rspec/allow_event.rb +5 -1
  37. data/lib/aasm/rspec/allow_transition_to.rb +5 -1
  38. data/lib/aasm/rspec/transition_from.rb +8 -4
  39. data/lib/aasm/state_machine.rb +6 -12
  40. data/lib/aasm/state_machine_store.rb +76 -0
  41. data/lib/aasm/version.rb +1 -1
  42. data/lib/aasm.rb +8 -2
  43. data/lib/generators/aasm/aasm_generator.rb +16 -0
  44. data/lib/generators/aasm/orm_helpers.rb +41 -0
  45. data/lib/generators/active_record/aasm_generator.rb +40 -0
  46. data/lib/generators/active_record/templates/migration.rb +8 -0
  47. data/lib/generators/active_record/templates/migration_existing.rb +5 -0
  48. data/lib/generators/mongoid/aasm_generator.rb +28 -0
  49. data/lib/generators/nobrainer/aasm_generator.rb +28 -0
  50. data/lib/motion-aasm.rb +37 -0
  51. metadata +104 -259
  52. data/.document +0 -6
  53. data/.gitignore +0 -19
  54. data/.travis.yml +0 -37
  55. data/API +0 -34
  56. data/CHANGELOG.md +0 -272
  57. data/CODE_OF_CONDUCT.md +0 -13
  58. data/Gemfile +0 -15
  59. data/HOWTO +0 -12
  60. data/PLANNED_CHANGES.md +0 -11
  61. data/README_FROM_VERSION_3_TO_4.md +0 -240
  62. data/Rakefile +0 -26
  63. data/aasm.gemspec +0 -31
  64. data/callbacks.txt +0 -51
  65. data/gemfiles/rails_3.2.gemfile +0 -14
  66. data/gemfiles/rails_4.0.gemfile +0 -12
  67. data/gemfiles/rails_4.0_mongo_mapper.gemfile +0 -14
  68. data/gemfiles/rails_4.1.gemfile +0 -12
  69. data/gemfiles/rails_4.1_mongo_mapper.gemfile +0 -14
  70. data/gemfiles/rails_4.2.gemfile +0 -12
  71. data/gemfiles/rails_4.2_mongo_mapper.gemfile +0 -14
  72. data/gemfiles/rails_4.2_mongoid_5.gemfile +0 -12
  73. data/lib/aasm/persistence/mongo_mapper_persistence.rb +0 -157
  74. data/spec/database.rb +0 -63
  75. data/spec/database.yml +0 -3
  76. data/spec/en.yml +0 -9
  77. data/spec/en_deprecated_style.yml +0 -10
  78. data/spec/models/active_record/basic_active_record_two_state_machines_example.rb +0 -25
  79. data/spec/models/active_record/complex_active_record_example.rb +0 -33
  80. data/spec/models/active_record/derivate_new_dsl.rb +0 -7
  81. data/spec/models/active_record/false_state.rb +0 -35
  82. data/spec/models/active_record/gate.rb +0 -39
  83. data/spec/models/active_record/localizer_test_model.rb +0 -34
  84. data/spec/models/active_record/no_direct_assignment.rb +0 -21
  85. data/spec/models/active_record/no_scope.rb +0 -21
  86. data/spec/models/active_record/persisted_state.rb +0 -12
  87. data/spec/models/active_record/provided_and_persisted_state.rb +0 -24
  88. data/spec/models/active_record/reader.rb +0 -7
  89. data/spec/models/active_record/readme_job.rb +0 -21
  90. data/spec/models/active_record/simple_new_dsl.rb +0 -17
  91. data/spec/models/active_record/thief.rb +0 -29
  92. data/spec/models/active_record/transient.rb +0 -6
  93. data/spec/models/active_record/with_enum.rb +0 -39
  94. data/spec/models/active_record/with_false_enum.rb +0 -31
  95. data/spec/models/active_record/with_true_enum.rb +0 -39
  96. data/spec/models/active_record/writer.rb +0 -6
  97. data/spec/models/basic_two_state_machines_example.rb +0 -25
  98. data/spec/models/callbacks/basic.rb +0 -78
  99. data/spec/models/callbacks/basic_multiple.rb +0 -75
  100. data/spec/models/callbacks/guard_within_block.rb +0 -66
  101. data/spec/models/callbacks/guard_within_block_multiple.rb +0 -66
  102. data/spec/models/callbacks/multiple_transitions_transition_guard.rb +0 -65
  103. data/spec/models/callbacks/multiple_transitions_transition_guard_multiple.rb +0 -65
  104. data/spec/models/callbacks/private_method.rb +0 -44
  105. data/spec/models/callbacks/private_method_multiple.rb +0 -44
  106. data/spec/models/callbacks/with_args.rb +0 -61
  107. data/spec/models/callbacks/with_args_multiple.rb +0 -61
  108. data/spec/models/callbacks/with_state_arg.rb +0 -26
  109. data/spec/models/callbacks/with_state_arg_multiple.rb +0 -26
  110. data/spec/models/complex_example.rb +0 -222
  111. data/spec/models/conversation.rb +0 -93
  112. data/spec/models/default_state.rb +0 -12
  113. data/spec/models/double_definer.rb +0 -21
  114. data/spec/models/foo.rb +0 -92
  115. data/spec/models/foo_callback_multiple.rb +0 -45
  116. data/spec/models/guardian.rb +0 -48
  117. data/spec/models/guardian_multiple.rb +0 -48
  118. data/spec/models/initial_state_proc.rb +0 -31
  119. data/spec/models/invalid_persistor.rb +0 -31
  120. data/spec/models/mongo_mapper/complex_mongo_mapper_example.rb +0 -37
  121. data/spec/models/mongo_mapper/no_scope_mongo_mapper.rb +0 -21
  122. data/spec/models/mongo_mapper/simple_mongo_mapper.rb +0 -23
  123. data/spec/models/mongo_mapper/simple_new_dsl_mongo_mapper.rb +0 -25
  124. data/spec/models/mongoid/complex_mongoid_example.rb +0 -37
  125. data/spec/models/mongoid/no_scope_mongoid.rb +0 -21
  126. data/spec/models/mongoid/simple_mongoid.rb +0 -23
  127. data/spec/models/mongoid/simple_new_dsl_mongoid.rb +0 -25
  128. data/spec/models/no_initial_state.rb +0 -25
  129. data/spec/models/not_auto_loaded/process.rb +0 -21
  130. data/spec/models/parametrised_event.rb +0 -29
  131. data/spec/models/parametrised_event_multiple.rb +0 -29
  132. data/spec/models/process_with_new_dsl.rb +0 -31
  133. data/spec/models/provided_state.rb +0 -24
  134. data/spec/models/sequel/complex_sequel_example.rb +0 -45
  135. data/spec/models/sequel/sequel_multiple.rb +0 -25
  136. data/spec/models/sequel/sequel_simple.rb +0 -25
  137. data/spec/models/silencer.rb +0 -27
  138. data/spec/models/simple_example.rb +0 -15
  139. data/spec/models/simple_multiple_example.rb +0 -30
  140. data/spec/models/state_machine_with_failed_event.rb +0 -12
  141. data/spec/models/sub_class.rb +0 -7
  142. data/spec/models/sub_class_with_more_states.rb +0 -18
  143. data/spec/models/sub_classing.rb +0 -3
  144. data/spec/models/super_class.rb +0 -46
  145. data/spec/models/this_name_better_not_be_in_use.rb +0 -11
  146. data/spec/models/transactor.rb +0 -53
  147. data/spec/models/valid_state_name.rb +0 -23
  148. data/spec/models/validator.rb +0 -79
  149. data/spec/models/worker.rb +0 -2
  150. data/spec/spec_helper.rb +0 -25
  151. data/spec/unit/api_spec.rb +0 -77
  152. data/spec/unit/basic_two_state_machines_example_spec.rb +0 -10
  153. data/spec/unit/callback_multiple_spec.rb +0 -295
  154. data/spec/unit/callbacks_spec.rb +0 -296
  155. data/spec/unit/complex_example_spec.rb +0 -84
  156. data/spec/unit/complex_multiple_example_spec.rb +0 -99
  157. data/spec/unit/edge_cases_spec.rb +0 -16
  158. data/spec/unit/event_multiple_spec.rb +0 -73
  159. data/spec/unit/event_naming_spec.rb +0 -11
  160. data/spec/unit/event_spec.rb +0 -322
  161. data/spec/unit/guard_multiple_spec.rb +0 -60
  162. data/spec/unit/guard_spec.rb +0 -60
  163. data/spec/unit/initial_state_multiple_spec.rb +0 -15
  164. data/spec/unit/initial_state_spec.rb +0 -12
  165. data/spec/unit/inspection_multiple_spec.rb +0 -201
  166. data/spec/unit/inspection_spec.rb +0 -111
  167. data/spec/unit/localizer_spec.rb +0 -76
  168. data/spec/unit/memory_leak_spec.rb +0 -38
  169. data/spec/unit/new_dsl_spec.rb +0 -12
  170. data/spec/unit/persistence/active_record_persistence_multiple_spec.rb +0 -573
  171. data/spec/unit/persistence/active_record_persistence_spec.rb +0 -552
  172. data/spec/unit/persistence/mongo_mapper_persistence_multiple_spec.rb +0 -146
  173. data/spec/unit/persistence/mongo_mapper_persistence_spec.rb +0 -93
  174. data/spec/unit/persistence/mongoid_persistence_multiple_spec.rb +0 -127
  175. data/spec/unit/persistence/mongoid_persistence_spec.rb +0 -79
  176. data/spec/unit/persistence/sequel_persistence_multiple_spec.rb +0 -153
  177. data/spec/unit/persistence/sequel_persistence_spec.rb +0 -100
  178. data/spec/unit/readme_spec.rb +0 -42
  179. data/spec/unit/reloading_spec.rb +0 -15
  180. data/spec/unit/rspec_matcher_spec.rb +0 -79
  181. data/spec/unit/simple_example_spec.rb +0 -42
  182. data/spec/unit/simple_multiple_example_spec.rb +0 -63
  183. data/spec/unit/state_spec.rb +0 -89
  184. data/spec/unit/subclassing_multiple_spec.rb +0 -39
  185. data/spec/unit/subclassing_spec.rb +0 -31
  186. data/spec/unit/transition_spec.rb +0 -291
data/lib/aasm/aasm.rb CHANGED
@@ -1,4 +1,6 @@
1
1
  module AASM
2
+ # this is used internally as an argument default value to represent no value
3
+ NO_VALUE = :_aasm_no_value
2
4
 
3
5
  # provide a state machine for the including class
4
6
  # make sure to load class methods as well
@@ -8,7 +10,7 @@ module AASM
8
10
 
9
11
  # do not overwrite existing state machines, which could have been created by
10
12
  # inheritance, see class method inherited
11
- AASM::StateMachine[base] ||= {}
13
+ AASM::StateMachineStore.register(base)
12
14
 
13
15
  AASM::Persistence.load_persistence(base)
14
16
  super
@@ -17,10 +19,8 @@ module AASM
17
19
  module ClassMethods
18
20
  # make sure inheritance (aka subclassing) works with AASM
19
21
  def inherited(base)
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
+ AASM::StateMachineStore.register(base, self)
23
+
24
24
  super
25
25
  end
26
26
 
@@ -36,9 +36,15 @@ module AASM
36
36
  options = args[0] || {}
37
37
  end
38
38
 
39
- AASM::StateMachine[self][state_machine_name] ||= AASM::StateMachine.new(state_machine_name)
39
+ AASM::StateMachineStore.fetch(self, true).register(state_machine_name, AASM::StateMachine.new(state_machine_name))
40
+
41
+ # use a default despite the DSL configuration default.
42
+ # this is because configuration hasn't been setup for the AASM class but we are accessing a DSL option already for the class.
43
+ aasm_klass = options[:with_klass] || AASM::Base
40
44
 
41
- @aasm ||= {}
45
+ raise ArgumentError, "The class #{aasm_klass} must inherit from AASM::Base!" unless aasm_klass.ancestors.include?(AASM::Base)
46
+
47
+ @aasm ||= Concurrent::Map.new
42
48
  if @aasm[state_machine_name]
43
49
  # make sure to use provided options
44
50
  options.each do |key, value|
@@ -46,10 +52,10 @@ module AASM
46
52
  end
47
53
  else
48
54
  # create a new base
49
- @aasm[state_machine_name] = AASM::Base.new(
55
+ @aasm[state_machine_name] = aasm_klass.new(
50
56
  self,
51
57
  state_machine_name,
52
- AASM::StateMachine[self][state_machine_name],
58
+ AASM::StateMachineStore.fetch(self, true).machine(state_machine_name),
53
59
  options
54
60
  )
55
61
  end
@@ -60,13 +66,18 @@ module AASM
60
66
 
61
67
  # this is the entry point for all instance-level access to AASM
62
68
  def aasm(name=:default)
63
- unless AASM::StateMachine[self.class][name.to_sym]
69
+ unless AASM::StateMachineStore.fetch(self.class, true).machine(name)
64
70
  raise AASM::UnknownStateMachineError.new("There is no state machine with the name '#{name}' defined in #{self.class.name}!")
65
71
  end
66
- @aasm ||= {}
72
+ @aasm ||= Concurrent::Map.new
67
73
  @aasm[name.to_sym] ||= AASM::InstanceBase.new(self, name.to_sym)
68
74
  end
69
75
 
76
+ def initialize_dup(other)
77
+ @aasm = Concurrent::Map.new
78
+ super
79
+ end
80
+
70
81
  private
71
82
 
72
83
  # Takes args and a from state and removes the first
@@ -88,65 +99,91 @@ private
88
99
  begin
89
100
  old_state = aasm(state_machine_name).state_object_for_name(aasm(state_machine_name).current_state)
90
101
 
91
- # new event before callback
92
- event.fire_callbacks(
93
- :before,
94
- self,
95
- *process_args(event, aasm(state_machine_name).current_state, *args)
96
- )
102
+ fire_default_callbacks(event, *process_args(event, aasm(state_machine_name).current_state, *args))
97
103
 
98
104
  if may_fire_to = event.may_fire?(self, *args)
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))
103
-
105
+ fire_exit_callbacks(old_state, *process_args(event, aasm(state_machine_name).current_state, *args))
104
106
  if new_state_name = event.fire(self, {:may_fire => may_fire_to}, *args)
105
107
  aasm_fired(state_machine_name, event, old_state, new_state_name, options, *args, &block)
106
108
  else
107
- aasm_failed(state_machine_name, event_name, old_state)
109
+ aasm_failed(state_machine_name, event_name, old_state, event.failed_callbacks)
108
110
  end
109
111
  else
110
- aasm_failed(state_machine_name, event_name, old_state)
112
+ aasm_failed(state_machine_name, event_name, old_state, event.failed_callbacks)
111
113
  end
112
114
  rescue StandardError => e
113
- event.fire_callbacks(:error, self, e, *process_args(event, aasm(state_machine_name).current_state, *args)) || raise(e)
115
+ event.fire_callbacks(:error, self, e, *process_args(event, aasm(state_machine_name).current_state, *args)) ||
116
+ event.fire_global_callbacks(:error_on_all_events, self, e, *process_args(event, aasm(state_machine_name).current_state, *args)) ||
117
+ raise(e)
118
+ false
119
+ ensure
120
+ event.fire_callbacks(:ensure, self, *process_args(event, aasm(state_machine_name).current_state, *args))
121
+ event.fire_global_callbacks(:ensure_on_all_events, self, *process_args(event, aasm(state_machine_name).current_state, *args))
114
122
  end
115
123
  end
116
124
 
125
+ def fire_default_callbacks(event, *processed_args)
126
+ event.fire_global_callbacks(
127
+ :before_all_events,
128
+ self,
129
+ *processed_args
130
+ )
131
+
132
+ # new event before callback
133
+ event.fire_callbacks(
134
+ :before,
135
+ self,
136
+ *processed_args
137
+ )
138
+ end
139
+
140
+ def fire_exit_callbacks(old_state, *processed_args)
141
+ old_state.fire_callbacks(:before_exit, self, *processed_args)
142
+ old_state.fire_callbacks(:exit, self, *processed_args)
143
+ end
144
+
117
145
  def aasm_fired(state_machine_name, event, old_state, new_state_name, options, *args)
118
146
  persist = options[:persist]
119
147
 
120
148
  new_state = aasm(state_machine_name).state_object_for_name(new_state_name)
149
+ callback_args = process_args(event, aasm(state_machine_name).current_state, *args)
121
150
 
122
- new_state.fire_callbacks(:before_enter, self,
123
- *process_args(event, aasm(state_machine_name).current_state, *args))
151
+ new_state.fire_callbacks(:before_enter, self, *callback_args)
124
152
 
125
- new_state.fire_callbacks(:enter, self,
126
- *process_args(event, aasm(state_machine_name).current_state, *args)) # TODO: remove for AASM 4?
153
+ new_state.fire_callbacks(:enter, self, *callback_args) # TODO: remove for AASM 4?
127
154
 
128
155
  persist_successful = true
129
156
  if persist
130
157
  persist_successful = aasm(state_machine_name).set_current_state_with_persistence(new_state_name)
131
158
  if persist_successful
132
159
  yield if block_given?
133
- event.fire_callbacks(:success, self)
160
+ event.fire_callbacks(:before_success, self, *callback_args)
161
+ event.fire_transition_callbacks(self, *process_args(event, old_state.name, *args))
162
+ event.fire_callbacks(:success, self, *callback_args)
134
163
  end
135
164
  else
136
165
  aasm(state_machine_name).current_state = new_state_name
137
166
  yield if block_given?
138
167
  end
139
168
 
169
+ binding_event = event.options[:binding_event]
170
+ if binding_event
171
+ __send__("#{binding_event}#{'!' if persist}")
172
+ end
173
+
140
174
  if persist_successful
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))
175
+ old_state.fire_callbacks(:after_exit, self, *callback_args)
176
+ new_state.fire_callbacks(:after_enter, self, *callback_args)
145
177
  event.fire_callbacks(
146
178
  :after,
147
179
  self,
148
180
  *process_args(event, old_state.name, *args)
149
181
  )
182
+ event.fire_global_callbacks(
183
+ :after_all_events,
184
+ self,
185
+ *process_args(event, old_state.name, *args)
186
+ )
150
187
 
151
188
  self.aasm_event_fired(event.name, old_state.name, aasm(state_machine_name).current_state) if self.respond_to?(:aasm_event_fired)
152
189
  else
@@ -156,13 +193,13 @@ private
156
193
  persist_successful
157
194
  end
158
195
 
159
- def aasm_failed(state_machine_name, event_name, old_state)
196
+ def aasm_failed(state_machine_name, event_name, old_state, failures = [])
160
197
  if self.respond_to?(:aasm_event_failed)
161
198
  self.aasm_event_failed(event_name, old_state.name)
162
199
  end
163
200
 
164
- if AASM::StateMachine[self.class][state_machine_name].config.whiny_transitions
165
- raise AASM::InvalidTransition.new(self, event_name, state_machine_name)
201
+ if AASM::StateMachineStore.fetch(self.class, true).machine(state_machine_name).config.whiny_transitions
202
+ raise AASM::InvalidTransition.new(self, event_name, state_machine_name, failures)
166
203
  else
167
204
  false
168
205
  end
data/lib/aasm/base.rb CHANGED
@@ -1,12 +1,14 @@
1
+ require 'logger'
2
+
1
3
  module AASM
2
4
  class Base
3
5
 
4
- attr_reader :state_machine
6
+ attr_reader :klass, :state_machine
5
7
 
6
8
  def initialize(klass, name, state_machine, options={}, &block)
7
9
  @klass = klass
8
10
  @name = name
9
- # @state_machine = @klass.aasm(@name).state_machine
11
+ # @state_machine = klass.aasm(@name).state_machine
10
12
  @state_machine = state_machine
11
13
  @state_machine.config.column ||= (options[:column] || default_column).to_sym
12
14
  # @state_machine.config.column = options[:column].to_sym if options[:column] # master
@@ -21,27 +23,43 @@ module AASM
21
23
  # don't store any new state if the model is invalid (in ActiveRecord)
22
24
  configure :skip_validation_on_save, false
23
25
 
26
+ # raise if the model is invalid (in ActiveRecord)
27
+ configure :whiny_persistence, false
28
+
29
+ # Use transactions (in ActiveRecord)
30
+ configure :use_transactions, true
31
+
24
32
  # use requires_new for nested transactions (in ActiveRecord)
25
33
  configure :requires_new_transaction, true
26
34
 
35
+ # use pessimistic locking (in ActiveRecord)
36
+ # true for FOR UPDATE lock
37
+ # string for a specific lock type i.e. FOR UPDATE NOWAIT
38
+ configure :requires_lock, false
39
+
40
+ # automatically set `"#{state_name}_at" = ::Time.now` on state changes
41
+ configure :timestamps, false
42
+
27
43
  # set to true to forbid direct assignment of aasm_state column (in ActiveRecord)
28
44
  configure :no_direct_assignment, false
29
45
 
46
+ # allow a AASM::Base sub-class to be used for state machine
47
+ configure :with_klass, AASM::Base
48
+
30
49
  configure :enum, nil
31
50
 
51
+ # Set to true to namespace reader methods and constants
52
+ configure :namespace, false
53
+
54
+ # Configure a logger, with default being a Logger to STDERR
55
+ configure :logger, Logger.new(STDERR)
56
+
57
+ # setup timestamp-setting callback if enabled
58
+ setup_timestamps(@name)
59
+
32
60
  # make sure to raise an error if no_direct_assignment is enabled
33
61
  # 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
43
- end
44
- )
62
+ setup_no_direct_assignment(@name)
45
63
  end
46
64
 
47
65
  # This method is both a getter and a setter
@@ -63,21 +81,29 @@ module AASM
63
81
  end
64
82
 
65
83
  # define a state
66
- def state(name, options={})
67
- @state_machine.add_state(name, @klass, options)
84
+ # args
85
+ # [0] state
86
+ # [1] options (or nil)
87
+ # or
88
+ # [0] state
89
+ # [1..] state
90
+ def state(*args)
91
+ names, options = interpret_state_args(args)
92
+ names.each do |name|
93
+ @state_machine.add_state(name, klass, options)
68
94
 
69
- if @klass.instance_methods.include?("#{name}?")
70
- warn "#{@klass.name}: The aasm state name #{name} is already used!"
71
- end
95
+ aasm_name = @name.to_sym
96
+ state = name.to_sym
72
97
 
73
- @klass.class_eval <<-EORUBY, __FILE__, __LINE__ + 1
74
- def #{name}?
75
- aasm(:#{@name}).current_state == :#{name}
98
+ method_name = namespace? ? "#{namespace}_#{name}" : name
99
+ safely_define_method klass, "#{method_name}?", -> do
100
+ aasm(aasm_name).current_state == state
76
101
  end
77
- EORUBY
78
102
 
79
- unless @klass.const_defined?("STATE_#{name.upcase}")
80
- @klass.const_set("STATE_#{name.upcase}", name)
103
+ const_name = namespace? ? "STATE_#{namespace.upcase}_#{name.upcase}" : "STATE_#{name.upcase}"
104
+ unless klass.const_defined?(const_name)
105
+ klass.const_set(const_name, name)
106
+ end
81
107
  end
82
108
  end
83
109
 
@@ -85,34 +111,65 @@ module AASM
85
111
  def event(name, options={}, &block)
86
112
  @state_machine.add_event(name, options, &block)
87
113
 
88
- if @klass.instance_methods.include?("may_#{name}?".to_sym)
89
- warn "#{@klass.name}: The aasm event name #{name} is already used!"
90
- end
114
+ aasm_name = @name.to_sym
115
+ event = name.to_sym
91
116
 
92
117
  # an addition over standard aasm so that, before firing an event, you can ask
93
118
  # may_event? and get back a boolean that tells you whether the guard method
94
119
  # on the transition will let this happen.
95
- @klass.class_eval <<-EORUBY, __FILE__, __LINE__ + 1
96
- def may_#{name}?(*args)
97
- aasm(:#{@name}).may_fire_event?(:#{name}, *args)
98
- end
120
+ safely_define_method klass, "may_#{name}?", ->(*args) do
121
+ aasm(aasm_name).may_fire_event?(event, *args)
122
+ end
99
123
 
100
- def #{name}!(*args, &block)
101
- aasm(:#{@name}).current_event = :#{name}!
102
- aasm_fire_event(:#{@name}, :#{name}, {:persist => true}, *args, &block)
103
- end
124
+ safely_define_method klass, "#{name}!", ->(*args, &block) do
125
+ aasm(aasm_name).current_event = :"#{name}!"
126
+ aasm_fire_event(aasm_name, event, {:persist => true}, *args, &block)
127
+ end
128
+
129
+ safely_define_method klass, name, ->(*args, &block) do
130
+ aasm(aasm_name).current_event = event
131
+ aasm_fire_event(aasm_name, event, {:persist => false}, *args, &block)
132
+ end
133
+
134
+ skip_instance_level_validation(event, name, aasm_name, klass)
135
+
136
+ # Create aliases for the event methods. Keep the old names to maintain backwards compatibility.
137
+ if namespace?
138
+ klass.send(:alias_method, "may_#{name}_#{namespace}?", "may_#{name}?")
139
+ klass.send(:alias_method, "#{name}_#{namespace}!", "#{name}!")
140
+ klass.send(:alias_method, "#{name}_#{namespace}", name)
141
+ end
104
142
 
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
143
  end
111
144
 
112
145
  def after_all_transitions(*callbacks, &block)
113
146
  @state_machine.add_global_callbacks(:after_all_transitions, *callbacks, &block)
114
147
  end
115
148
 
149
+ def after_all_transactions(*callbacks, &block)
150
+ @state_machine.add_global_callbacks(:after_all_transactions, *callbacks, &block)
151
+ end
152
+
153
+ def before_all_transactions(*callbacks, &block)
154
+ @state_machine.add_global_callbacks(:before_all_transactions, *callbacks, &block)
155
+ end
156
+
157
+ def before_all_events(*callbacks, &block)
158
+ @state_machine.add_global_callbacks(:before_all_events, *callbacks, &block)
159
+ end
160
+
161
+ def after_all_events(*callbacks, &block)
162
+ @state_machine.add_global_callbacks(:after_all_events, *callbacks, &block)
163
+ end
164
+
165
+ def error_on_all_events(*callbacks, &block)
166
+ @state_machine.add_global_callbacks(:error_on_all_events, *callbacks, &block)
167
+ end
168
+
169
+ def ensure_on_all_events(*callbacks, &block)
170
+ @state_machine.add_global_callbacks(:ensure_on_all_events, *callbacks, &block)
171
+ end
172
+
116
173
  def states
117
174
  @state_machine.states
118
175
  end
@@ -123,7 +180,7 @@ module AASM
123
180
 
124
181
  # aasm.event(:event_name).human?
125
182
  def human_event_name(event) # event_name?
126
- AASM::Localizer.new.human_event_name(@klass, event)
183
+ AASM::Localizer.new.human_event_name(klass, event)
127
184
  end
128
185
 
129
186
  def states_for_select
@@ -134,6 +191,7 @@ module AASM
134
191
  if options[:transition]
135
192
  @state_machine.events[options[:transition]].transitions_to_state(state).flatten.map(&:from).flatten
136
193
  else
194
+
137
195
  events.map {|e| e.transitions_to_state(state)}.flatten.map(&:from).flatten
138
196
  end
139
197
  end
@@ -152,5 +210,94 @@ module AASM
152
210
  end
153
211
  end
154
212
 
213
+ def safely_define_method(klass, method_name, method_definition)
214
+ # Warn if method exists and it did not originate from an enum
215
+ if klass.method_defined?(method_name) &&
216
+ ! ( @state_machine.config.enum &&
217
+ klass.respond_to?(:defined_enums) &&
218
+ klass.defined_enums.values.any?{ |methods|
219
+ methods.keys{| enum | enum + '?' == method_name }
220
+ })
221
+ unless AASM::Configuration.hide_warnings
222
+ @state_machine.config.logger.warn "#{klass.name}: overriding method '#{method_name}'!"
223
+ end
224
+ end
225
+
226
+ klass.send(:define_method, method_name, method_definition).tap do |sym|
227
+ apply_ruby2_keyword(klass, sym)
228
+ end
229
+ end
230
+
231
+ def apply_ruby2_keyword(klass, sym)
232
+ if RUBY_VERSION >= '2.7.1'
233
+ if klass.instance_method(sym).parameters.find { |type, _| type.to_s.start_with?('rest') }
234
+ # If there is a place where you are receiving in *args, do ruby2_keywords.
235
+ klass.module_eval do
236
+ ruby2_keywords sym
237
+ end
238
+ end
239
+ end
240
+ end
241
+
242
+ def namespace?
243
+ !!@state_machine.config.namespace
244
+ end
245
+
246
+ def namespace
247
+ if @state_machine.config.namespace == true
248
+ @name
249
+ else
250
+ @state_machine.config.namespace
251
+ end
252
+ end
253
+
254
+ def interpret_state_args(args)
255
+ if args.last.is_a?(Hash) && args.size == 2
256
+ [[args.first], args.last]
257
+ elsif args.size > 0
258
+ [args, {}]
259
+ else
260
+ raise "count not parse states: #{args}"
261
+ end
262
+ end
263
+
264
+ def skip_instance_level_validation(event, name, aasm_name, klass)
265
+ # Overrides the skip_validation config for an instance (If skip validation is set to false in original config) and
266
+ # restores it back to the original value after the event is fired.
267
+ safely_define_method klass, "#{name}_without_validation!", ->(*args, &block) do
268
+ original_config = AASM::StateMachineStore.fetch(self.class, true).machine(aasm_name).config.skip_validation_on_save
269
+ begin
270
+ AASM::StateMachineStore.fetch(self.class, true).machine(aasm_name).config.skip_validation_on_save = true unless original_config
271
+ aasm(aasm_name).current_event = :"#{name}!"
272
+ aasm_fire_event(aasm_name, event, {:persist => true}, *args, &block)
273
+ ensure
274
+ AASM::StateMachineStore.fetch(self.class, true).machine(aasm_name).config.skip_validation_on_save = original_config
275
+ end
276
+ end
277
+ end
278
+
279
+ def setup_timestamps(aasm_name)
280
+ return unless @state_machine.config.timestamps
281
+
282
+ after_all_transitions do
283
+ if self.class.aasm(:"#{aasm_name}").state_machine.config.timestamps
284
+ ts_setter = "#{aasm(aasm_name).to_state}_at="
285
+ respond_to?(ts_setter) && send(ts_setter, ::Time.now)
286
+ end
287
+ end
288
+ end
289
+
290
+ def setup_no_direct_assignment(aasm_name)
291
+ return unless @state_machine.config.no_direct_assignment
292
+
293
+ @klass.send(:define_method, "#{@state_machine.config.column}=") do |state_name|
294
+ if self.class.aasm(:"#{aasm_name}").state_machine.config.no_direct_assignment
295
+ raise AASM::NoDirectAssignmentError.new('direct assignment of AASM column has been disabled (see AASM configuration for this class)')
296
+ else
297
+ super(state_name)
298
+ end
299
+ end
300
+ end
301
+
155
302
  end
156
303
  end
@@ -9,15 +9,40 @@ module AASM
9
9
  # for all persistence layers: create named scopes for each state
10
10
  attr_accessor :create_scopes
11
11
 
12
- # for ActiveRecord: don't store any new state if the model is invalid
12
+ # for ActiveRecord: when the model is invalid, true -> raise, false -> return false
13
+ attr_accessor :whiny_persistence
14
+
15
+ # for ActiveRecord: store the new state even if the model is invalid and return true
13
16
  attr_accessor :skip_validation_on_save
14
17
 
18
+ # for ActiveRecord: use transactions
19
+ attr_accessor :use_transactions
20
+
15
21
  # for ActiveRecord: use requires_new for nested transactions?
16
22
  attr_accessor :requires_new_transaction
17
23
 
24
+ # for ActiveRecord: use pessimistic locking
25
+ attr_accessor :requires_lock
26
+
27
+ # automatically set `"#{state_name}_at" = ::Time.now` on state changes
28
+ attr_accessor :timestamps
29
+
18
30
  # forbid direct assignment in aasm_state column (in ActiveRecord)
19
31
  attr_accessor :no_direct_assignment
20
32
 
33
+ # allow a AASM::Base sub-class to be used for state machine
34
+ attr_accessor :with_klass
35
+
21
36
  attr_accessor :enum
37
+
38
+ # namespace reader methods and constants
39
+ attr_accessor :namespace
40
+
41
+ # Configure a logger, with default being a Logger to STDERR
42
+ attr_accessor :logger
43
+
44
+ class << self
45
+ attr_accessor :hide_warnings
46
+ end
22
47
  end
23
- end
48
+ end