aasm 2.1.1 → 5.2.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 (275) hide show
  1. checksums.yaml +7 -0
  2. data/.document +6 -0
  3. data/.github/ISSUE_TEMPLATE/bug_report.md +27 -0
  4. data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  5. data/.gitignore +20 -0
  6. data/.travis.yml +82 -0
  7. data/API +34 -0
  8. data/Appraisals +67 -0
  9. data/CHANGELOG.md +453 -0
  10. data/CODE_OF_CONDUCT.md +13 -0
  11. data/CONTRIBUTING.md +24 -0
  12. data/Dockerfile +44 -0
  13. data/Gemfile +6 -0
  14. data/Gemfile.lock_old +151 -0
  15. data/HOWTO +12 -0
  16. data/{MIT-LICENSE → LICENSE} +1 -1
  17. data/PLANNED_CHANGES.md +11 -0
  18. data/README.md +1524 -0
  19. data/README_FROM_VERSION_3_TO_4.md +240 -0
  20. data/Rakefile +20 -84
  21. data/TESTING.md +25 -0
  22. data/aasm.gemspec +37 -0
  23. data/docker-compose.yml +40 -0
  24. data/gemfiles/norails.gemfile +10 -0
  25. data/gemfiles/rails_4.2.gemfile +17 -0
  26. data/gemfiles/rails_4.2_mongoid_5.gemfile +12 -0
  27. data/gemfiles/rails_4.2_nobrainer.gemfile +9 -0
  28. data/gemfiles/rails_5.0.gemfile +14 -0
  29. data/gemfiles/rails_5.0_nobrainer.gemfile +9 -0
  30. data/gemfiles/rails_5.1.gemfile +14 -0
  31. data/gemfiles/rails_5.2.gemfile +14 -0
  32. data/lib/aasm/aasm.rb +160 -137
  33. data/lib/aasm/base.rb +290 -0
  34. data/lib/aasm/configuration.rb +48 -0
  35. data/lib/aasm/core/event.rb +177 -0
  36. data/lib/aasm/core/invoker.rb +129 -0
  37. data/lib/aasm/core/invokers/base_invoker.rb +75 -0
  38. data/lib/aasm/core/invokers/class_invoker.rb +52 -0
  39. data/lib/aasm/core/invokers/literal_invoker.rb +47 -0
  40. data/lib/aasm/core/invokers/proc_invoker.rb +59 -0
  41. data/lib/aasm/core/state.rb +91 -0
  42. data/lib/aasm/core/transition.rb +83 -0
  43. data/lib/aasm/dsl_helper.rb +32 -0
  44. data/lib/aasm/errors.rb +21 -0
  45. data/lib/aasm/instance_base.rb +133 -0
  46. data/lib/aasm/localizer.rb +64 -0
  47. data/lib/aasm/minitest/allow_event.rb +13 -0
  48. data/lib/aasm/minitest/allow_transition_to.rb +13 -0
  49. data/lib/aasm/minitest/have_state.rb +13 -0
  50. data/lib/aasm/minitest/transition_from.rb +21 -0
  51. data/lib/aasm/minitest.rb +5 -0
  52. data/lib/aasm/minitest_spec.rb +15 -0
  53. data/lib/aasm/persistence/active_record_persistence.rb +108 -173
  54. data/lib/aasm/persistence/base.rb +89 -0
  55. data/lib/aasm/persistence/core_data_query_persistence.rb +94 -0
  56. data/lib/aasm/persistence/dynamoid_persistence.rb +92 -0
  57. data/lib/aasm/persistence/mongoid_persistence.rb +126 -0
  58. data/lib/aasm/persistence/no_brainer_persistence.rb +105 -0
  59. data/lib/aasm/persistence/orm.rb +154 -0
  60. data/lib/aasm/persistence/plain_persistence.rb +26 -0
  61. data/lib/aasm/persistence/redis_persistence.rb +112 -0
  62. data/lib/aasm/persistence/sequel_persistence.rb +83 -0
  63. data/lib/aasm/persistence.rb +48 -10
  64. data/lib/aasm/rspec/allow_event.rb +26 -0
  65. data/lib/aasm/rspec/allow_transition_to.rb +26 -0
  66. data/lib/aasm/rspec/have_state.rb +22 -0
  67. data/lib/aasm/rspec/transition_from.rb +36 -0
  68. data/lib/aasm/rspec.rb +5 -0
  69. data/lib/aasm/state_machine.rb +40 -22
  70. data/lib/aasm/state_machine_store.rb +76 -0
  71. data/lib/aasm/version.rb +3 -0
  72. data/lib/aasm.rb +21 -1
  73. data/lib/generators/aasm/aasm_generator.rb +16 -0
  74. data/lib/generators/aasm/orm_helpers.rb +41 -0
  75. data/lib/generators/active_record/aasm_generator.rb +40 -0
  76. data/lib/generators/active_record/templates/migration.rb +8 -0
  77. data/lib/generators/active_record/templates/migration_existing.rb +5 -0
  78. data/lib/generators/mongoid/aasm_generator.rb +28 -0
  79. data/lib/generators/nobrainer/aasm_generator.rb +28 -0
  80. data/lib/motion-aasm.rb +37 -0
  81. data/spec/database.rb +57 -0
  82. data/spec/database.yml +3 -0
  83. data/spec/en.yml +9 -0
  84. data/spec/generators/active_record_generator_spec.rb +53 -0
  85. data/spec/generators/mongoid_generator_spec.rb +31 -0
  86. data/spec/generators/no_brainer_generator_spec.rb +29 -0
  87. data/spec/localizer_test_model_deprecated_style.yml +13 -0
  88. data/spec/localizer_test_model_new_style.yml +11 -0
  89. data/spec/models/active_record/active_record_callback.rb +93 -0
  90. data/spec/models/active_record/basic_active_record_two_state_machines_example.rb +25 -0
  91. data/spec/models/active_record/complex_active_record_example.rb +37 -0
  92. data/spec/models/active_record/derivate_new_dsl.rb +7 -0
  93. data/spec/models/active_record/false_state.rb +35 -0
  94. data/spec/models/active_record/gate.rb +39 -0
  95. data/spec/models/active_record/instance_level_skip_validation_example.rb +19 -0
  96. data/spec/models/active_record/invalid_persistor.rb +29 -0
  97. data/spec/models/active_record/localizer_test_model.rb +42 -0
  98. data/spec/models/active_record/namespaced.rb +16 -0
  99. data/spec/models/active_record/no_direct_assignment.rb +21 -0
  100. data/spec/models/active_record/no_scope.rb +21 -0
  101. data/spec/models/active_record/persisted_state.rb +12 -0
  102. data/spec/models/active_record/person.rb +23 -0
  103. data/spec/models/active_record/provided_and_persisted_state.rb +24 -0
  104. data/spec/models/active_record/reader.rb +7 -0
  105. data/spec/models/active_record/readme_job.rb +21 -0
  106. data/spec/models/active_record/silent_persistor.rb +29 -0
  107. data/spec/models/active_record/simple_new_dsl.rb +32 -0
  108. data/spec/models/active_record/thief.rb +29 -0
  109. data/spec/models/active_record/timestamp_example.rb +16 -0
  110. data/spec/models/active_record/transactor.rb +124 -0
  111. data/spec/models/active_record/transient.rb +6 -0
  112. data/spec/models/active_record/validator.rb +118 -0
  113. data/spec/models/active_record/with_enum.rb +39 -0
  114. data/spec/models/active_record/with_enum_without_column.rb +38 -0
  115. data/spec/models/active_record/with_false_enum.rb +31 -0
  116. data/spec/models/active_record/with_true_enum.rb +39 -0
  117. data/spec/models/active_record/work.rb +3 -0
  118. data/spec/models/active_record/worker.rb +2 -0
  119. data/spec/models/active_record/writer.rb +6 -0
  120. data/spec/models/basic_two_state_machines_example.rb +25 -0
  121. data/spec/models/callbacks/basic.rb +98 -0
  122. data/spec/models/callbacks/basic_multiple.rb +75 -0
  123. data/spec/models/callbacks/guard_within_block.rb +67 -0
  124. data/spec/models/callbacks/guard_within_block_multiple.rb +66 -0
  125. data/spec/models/callbacks/multiple_transitions_transition_guard.rb +66 -0
  126. data/spec/models/callbacks/multiple_transitions_transition_guard_multiple.rb +65 -0
  127. data/spec/models/callbacks/private_method.rb +44 -0
  128. data/spec/models/callbacks/private_method_multiple.rb +44 -0
  129. data/spec/models/callbacks/with_args.rb +62 -0
  130. data/spec/models/callbacks/with_args_multiple.rb +61 -0
  131. data/spec/models/callbacks/with_state_arg.rb +34 -0
  132. data/spec/models/callbacks/with_state_arg_multiple.rb +29 -0
  133. data/spec/models/complex_example.rb +222 -0
  134. data/spec/models/conversation.rb +93 -0
  135. data/spec/models/default_state.rb +12 -0
  136. data/spec/models/double_definer.rb +21 -0
  137. data/spec/models/dynamoid/complex_dynamoid_example.rb +37 -0
  138. data/spec/models/dynamoid/dynamoid_multiple.rb +18 -0
  139. data/spec/models/dynamoid/dynamoid_simple.rb +18 -0
  140. data/spec/models/foo.rb +106 -0
  141. data/spec/models/foo_callback_multiple.rb +45 -0
  142. data/spec/models/guard_arguments_check.rb +17 -0
  143. data/spec/models/guard_with_params.rb +24 -0
  144. data/spec/models/guard_with_params_multiple.rb +18 -0
  145. data/spec/models/guardian.rb +58 -0
  146. data/spec/models/guardian_multiple.rb +48 -0
  147. data/spec/models/guardian_without_from_specified.rb +18 -0
  148. data/spec/models/initial_state_proc.rb +31 -0
  149. data/spec/models/mongoid/complex_mongoid_example.rb +37 -0
  150. data/spec/models/mongoid/invalid_persistor_mongoid.rb +39 -0
  151. data/spec/models/mongoid/mongoid_relationships.rb +26 -0
  152. data/spec/models/mongoid/no_scope_mongoid.rb +21 -0
  153. data/spec/models/mongoid/silent_persistor_mongoid.rb +39 -0
  154. data/spec/models/mongoid/simple_mongoid.rb +23 -0
  155. data/spec/models/mongoid/simple_new_dsl_mongoid.rb +25 -0
  156. data/spec/models/mongoid/timestamp_example_mongoid.rb +20 -0
  157. data/spec/models/mongoid/validator_mongoid.rb +100 -0
  158. data/spec/models/multi_transitioner.rb +34 -0
  159. data/spec/models/multiple_transitions_that_differ_only_by_guard.rb +31 -0
  160. data/spec/models/namespaced_multiple_example.rb +42 -0
  161. data/spec/models/no_initial_state.rb +25 -0
  162. data/spec/models/nobrainer/complex_no_brainer_example.rb +36 -0
  163. data/spec/models/nobrainer/invalid_persistor_no_brainer.rb +39 -0
  164. data/spec/models/nobrainer/no_scope_no_brainer.rb +21 -0
  165. data/spec/models/nobrainer/nobrainer_relationships.rb +25 -0
  166. data/spec/models/nobrainer/silent_persistor_no_brainer.rb +39 -0
  167. data/spec/models/nobrainer/simple_new_dsl_nobrainer.rb +25 -0
  168. data/spec/models/nobrainer/simple_no_brainer.rb +23 -0
  169. data/spec/models/nobrainer/validator_no_brainer.rb +98 -0
  170. data/spec/models/not_auto_loaded/process.rb +21 -0
  171. data/spec/models/parametrised_event.rb +42 -0
  172. data/spec/models/parametrised_event_multiple.rb +29 -0
  173. data/spec/models/process_with_new_dsl.rb +31 -0
  174. data/spec/models/provided_state.rb +24 -0
  175. data/spec/models/redis/complex_redis_example.rb +40 -0
  176. data/spec/models/redis/redis_multiple.rb +20 -0
  177. data/spec/models/redis/redis_simple.rb +20 -0
  178. data/spec/models/sequel/complex_sequel_example.rb +46 -0
  179. data/spec/models/sequel/invalid_persistor.rb +52 -0
  180. data/spec/models/sequel/sequel_multiple.rb +25 -0
  181. data/spec/models/sequel/sequel_simple.rb +26 -0
  182. data/spec/models/sequel/silent_persistor.rb +50 -0
  183. data/spec/models/sequel/transactor.rb +112 -0
  184. data/spec/models/sequel/validator.rb +93 -0
  185. data/spec/models/sequel/worker.rb +12 -0
  186. data/spec/models/silencer.rb +27 -0
  187. data/spec/models/simple_custom_example.rb +53 -0
  188. data/spec/models/simple_example.rb +23 -0
  189. data/spec/models/simple_example_with_guard_args.rb +17 -0
  190. data/spec/models/simple_multiple_example.rb +42 -0
  191. data/spec/models/state_machine_with_failed_event.rb +20 -0
  192. data/spec/models/states_on_one_line_example.rb +8 -0
  193. data/spec/models/sub_class.rb +41 -0
  194. data/spec/models/sub_class_with_more_states.rb +18 -0
  195. data/spec/models/sub_classing.rb +3 -0
  196. data/spec/models/super_class.rb +46 -0
  197. data/spec/models/this_name_better_not_be_in_use.rb +11 -0
  198. data/spec/models/timestamps_example.rb +19 -0
  199. data/spec/models/timestamps_with_named_machine_example.rb +13 -0
  200. data/spec/models/valid_state_name.rb +23 -0
  201. data/spec/spec_helper.rb +41 -0
  202. data/spec/spec_helpers/active_record.rb +8 -0
  203. data/spec/spec_helpers/dynamoid.rb +35 -0
  204. data/spec/spec_helpers/mongoid.rb +26 -0
  205. data/spec/spec_helpers/nobrainer.rb +15 -0
  206. data/spec/spec_helpers/redis.rb +18 -0
  207. data/spec/spec_helpers/remove_warnings.rb +1 -0
  208. data/spec/spec_helpers/sequel.rb +7 -0
  209. data/spec/unit/abstract_class_spec.rb +27 -0
  210. data/spec/unit/api_spec.rb +104 -0
  211. data/spec/unit/basic_two_state_machines_example_spec.rb +10 -0
  212. data/spec/unit/callback_multiple_spec.rb +304 -0
  213. data/spec/unit/callbacks_spec.rb +521 -0
  214. data/spec/unit/complex_example_spec.rb +93 -0
  215. data/spec/unit/complex_multiple_example_spec.rb +115 -0
  216. data/spec/unit/edge_cases_spec.rb +16 -0
  217. data/spec/unit/event_multiple_spec.rb +73 -0
  218. data/spec/unit/event_naming_spec.rb +16 -0
  219. data/spec/unit/event_spec.rb +394 -0
  220. data/spec/unit/exception_spec.rb +11 -0
  221. data/spec/unit/guard_arguments_check_spec.rb +9 -0
  222. data/spec/unit/guard_multiple_spec.rb +60 -0
  223. data/spec/unit/guard_spec.rb +89 -0
  224. data/spec/unit/guard_with_params_multiple_spec.rb +10 -0
  225. data/spec/unit/guard_with_params_spec.rb +14 -0
  226. data/spec/unit/guard_without_from_specified_spec.rb +10 -0
  227. data/spec/unit/initial_state_multiple_spec.rb +15 -0
  228. data/spec/unit/initial_state_spec.rb +12 -0
  229. data/spec/unit/inspection_multiple_spec.rb +205 -0
  230. data/spec/unit/inspection_spec.rb +153 -0
  231. data/spec/unit/invoker_spec.rb +189 -0
  232. data/spec/unit/invokers/base_invoker_spec.rb +72 -0
  233. data/spec/unit/invokers/class_invoker_spec.rb +95 -0
  234. data/spec/unit/invokers/literal_invoker_spec.rb +86 -0
  235. data/spec/unit/invokers/proc_invoker_spec.rb +86 -0
  236. data/spec/unit/localizer_spec.rb +109 -0
  237. data/spec/unit/memory_leak_spec.rb +38 -0
  238. data/spec/unit/multiple_transitions_that_differ_only_by_guard_spec.rb +14 -0
  239. data/spec/unit/namespaced_multiple_example_spec.rb +75 -0
  240. data/spec/unit/new_dsl_spec.rb +12 -0
  241. data/spec/unit/override_warning_spec.rb +94 -0
  242. data/spec/unit/persistence/active_record_persistence_multiple_spec.rb +635 -0
  243. data/spec/unit/persistence/active_record_persistence_spec.rb +852 -0
  244. data/spec/unit/persistence/dynamoid_persistence_multiple_spec.rb +135 -0
  245. data/spec/unit/persistence/dynamoid_persistence_spec.rb +84 -0
  246. data/spec/unit/persistence/mongoid_persistence_multiple_spec.rb +200 -0
  247. data/spec/unit/persistence/mongoid_persistence_spec.rb +177 -0
  248. data/spec/unit/persistence/no_brainer_persistence_multiple_spec.rb +198 -0
  249. data/spec/unit/persistence/no_brainer_persistence_spec.rb +158 -0
  250. data/spec/unit/persistence/redis_persistence_multiple_spec.rb +88 -0
  251. data/spec/unit/persistence/redis_persistence_spec.rb +53 -0
  252. data/spec/unit/persistence/sequel_persistence_multiple_spec.rb +148 -0
  253. data/spec/unit/persistence/sequel_persistence_spec.rb +368 -0
  254. data/spec/unit/readme_spec.rb +41 -0
  255. data/spec/unit/reloading_spec.rb +15 -0
  256. data/spec/unit/rspec_matcher_spec.rb +88 -0
  257. data/spec/unit/simple_custom_example_spec.rb +39 -0
  258. data/spec/unit/simple_example_spec.rb +57 -0
  259. data/spec/unit/simple_multiple_example_spec.rb +91 -0
  260. data/spec/unit/state_spec.rb +105 -0
  261. data/spec/unit/states_on_one_line_example_spec.rb +16 -0
  262. data/spec/unit/subclassing_multiple_spec.rb +74 -0
  263. data/spec/unit/subclassing_spec.rb +46 -0
  264. data/spec/unit/timestamps_spec.rb +32 -0
  265. data/spec/unit/transition_spec.rb +436 -0
  266. data/test/minitest_helper.rb +57 -0
  267. data/test/unit/minitest_matcher_test.rb +80 -0
  268. metadata +607 -60
  269. data/CHANGELOG +0 -33
  270. data/README.rdoc +0 -122
  271. data/TODO +0 -9
  272. data/doc/jamis.rb +0 -591
  273. data/lib/aasm/event.rb +0 -76
  274. data/lib/aasm/state.rb +0 -35
  275. data/lib/aasm/state_transition.rb +0 -36
data/lib/aasm/aasm.rb CHANGED
@@ -1,185 +1,208 @@
1
- require File.join(File.dirname(__FILE__), 'event')
2
- require File.join(File.dirname(__FILE__), 'state')
3
- require File.join(File.dirname(__FILE__), 'state_machine')
4
- require File.join(File.dirname(__FILE__), 'persistence')
5
-
6
1
  module AASM
7
- def self.Version
8
- '2.1.1'
9
- end
10
-
11
- class InvalidTransition < RuntimeError
12
- end
13
-
14
- class UndefinedState < RuntimeError
15
- end
2
+ # this is used internally as an argument default value to represent no value
3
+ NO_VALUE = :_aasm_no_value
16
4
 
5
+ # provide a state machine for the including class
6
+ # make sure to load class methods as well
7
+ # initialize persistence for the state machine
17
8
  def self.included(base) #:nodoc:
18
9
  base.extend AASM::ClassMethods
19
- AASM::Persistence.set_persistence(base)
20
- unless AASM::StateMachine[base]
21
- AASM::StateMachine[base] = AASM::StateMachine.new('')
22
- end
10
+
11
+ # do not overwrite existing state machines, which could have been created by
12
+ # inheritance, see class method inherited
13
+ AASM::StateMachineStore.register(base)
14
+
15
+ AASM::Persistence.load_persistence(base)
16
+ super
23
17
  end
24
18
 
25
19
  module ClassMethods
26
- def inherited(klass)
27
- AASM::StateMachine[klass] = AASM::StateMachine[self].clone
20
+ # make sure inheritance (aka subclassing) works with AASM
21
+ def inherited(base)
22
+ AASM::StateMachineStore.register(base, self)
23
+
28
24
  super
29
25
  end
30
26
 
31
- def aasm_initial_state(set_state=nil)
32
- if set_state
33
- AASM::StateMachine[self].initial_state = set_state
27
+ # this is the entry point for all state and event definitions
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] || {}
34
33
  else
35
- AASM::StateMachine[self].initial_state
34
+ # using the default state_machine_name
35
+ state_machine_name = :default
36
+ options = args[0] || {}
36
37
  end
37
- end
38
38
 
39
- def aasm_initial_state=(state)
40
- AASM::StateMachine[self].initial_state = state
41
- end
39
+ AASM::StateMachineStore.fetch(self, true).register(state_machine_name, AASM::StateMachine.new(state_machine_name))
42
40
 
43
- def aasm_state(name, options={})
44
- sm = AASM::StateMachine[self]
45
- sm.create_state(name, options)
46
- sm.initial_state = name unless sm.initial_state
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
47
44
 
48
- define_method("#{name.to_s}?") do
49
- aasm_current_state == name
50
- end
51
- end
45
+ raise ArgumentError, "The class #{aasm_klass} must inherit from AASM::Base!" unless aasm_klass.ancestors.include?(AASM::Base)
52
46
 
53
- def aasm_event(name, options = {}, &block)
54
- sm = AASM::StateMachine[self]
55
-
56
- unless sm.events.has_key?(name)
57
- sm.events[name] = AASM::SupportingClasses::Event.new(name, options, &block)
58
- end
59
-
60
- define_method("#{name.to_s}!") do |*args|
61
- aasm_fire_event(name, true, *args)
62
- end
63
-
64
- define_method("#{name.to_s}") do |*args|
65
- aasm_fire_event(name, false, *args)
47
+ @aasm ||= Concurrent::Map.new
48
+ if @aasm[state_machine_name]
49
+ # make sure to use provided options
50
+ options.each do |key, value|
51
+ @aasm[state_machine_name].state_machine.config.send("#{key}=", value)
52
+ end
53
+ else
54
+ # create a new base
55
+ @aasm[state_machine_name] = aasm_klass.new(
56
+ self,
57
+ state_machine_name,
58
+ AASM::StateMachineStore.fetch(self, true).machine(state_machine_name),
59
+ options
60
+ )
66
61
  end
62
+ @aasm[state_machine_name].instance_eval(&block) if block # new DSL
63
+ @aasm[state_machine_name]
67
64
  end
65
+ end # ClassMethods
68
66
 
69
- def aasm_states
70
- AASM::StateMachine[self].states
67
+ # this is the entry point for all instance-level access to AASM
68
+ def aasm(name=:default)
69
+ unless AASM::StateMachineStore.fetch(self.class, true).machine(name)
70
+ raise AASM::UnknownStateMachineError.new("There is no state machine with the name '#{name}' defined in #{self.class.name}!")
71
71
  end
72
-
73
- def aasm_events
74
- AASM::StateMachine[self].events
75
- end
76
-
77
- def aasm_states_for_select
78
- AASM::StateMachine[self].states.map { |state| state.for_select }
79
- end
80
-
72
+ @aasm ||= Concurrent::Map.new
73
+ @aasm[name.to_sym] ||= AASM::InstanceBase.new(self, name.to_sym)
81
74
  end
82
75
 
83
- # Instance methods
84
- def aasm_current_state
85
- return @aasm_current_state if @aasm_current_state
76
+ def initialize_dup(other)
77
+ @aasm = Concurrent::Map.new
78
+ super
79
+ end
86
80
 
87
- if self.respond_to?(:aasm_read_state) || self.private_methods.include?('aasm_read_state')
88
- @aasm_current_state = aasm_read_state
81
+ private
82
+
83
+ # Takes args and a from state and removes the first
84
+ # element from args if it is a valid to_state for
85
+ # the event given the from_state
86
+ def process_args(event, from_state, *args)
87
+ # If the first arg doesn't respond to to_sym then
88
+ # it isn't a symbol or string so it can't be a state
89
+ # name anyway
90
+ return args unless args.first.respond_to?(:to_sym)
91
+ if event.transitions_from_state(from_state).map(&:to).flatten.include?(args.first)
92
+ return args[1..-1]
89
93
  end
90
- return @aasm_current_state if @aasm_current_state
91
- aasm_determine_state_name(self.class.aasm_initial_state)
94
+ return args
92
95
  end
93
96
 
94
- def aasm_events_for_current_state
95
- aasm_events_for_state(aasm_current_state)
96
- end
97
+ def aasm_fire_event(state_machine_name, event_name, options, *args, &block)
98
+ event = self.class.aasm(state_machine_name).state_machine.events[event_name]
99
+ begin
100
+ old_state = aasm(state_machine_name).state_object_for_name(aasm(state_machine_name).current_state)
97
101
 
98
- def aasm_events_for_state(state)
99
- events = self.class.aasm_events.values.select {|event| event.transitions_from_state?(state) }
100
- events.map {|event| event.name}
101
- end
102
+ fire_default_callbacks(event, *process_args(event, aasm(state_machine_name).current_state, *args))
102
103
 
103
- private
104
- def set_aasm_current_state_with_persistence(state)
105
- save_success = true
106
- if self.respond_to?(:aasm_write_state) || self.private_methods.include?('aasm_write_state')
107
- save_success = aasm_write_state(state)
104
+ if may_fire_to = event.may_fire?(self, *args)
105
+ fire_exit_callbacks(old_state, *process_args(event, aasm(state_machine_name).current_state, *args))
106
+ if new_state_name = event.fire(self, {:may_fire => may_fire_to}, *args)
107
+ aasm_fired(state_machine_name, event, old_state, new_state_name, options, *args, &block)
108
+ else
109
+ aasm_failed(state_machine_name, event_name, old_state, event.failed_callbacks)
110
+ end
111
+ else
112
+ aasm_failed(state_machine_name, event_name, old_state, event.failed_callbacks)
113
+ end
114
+ rescue StandardError => 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))
108
122
  end
109
- self.aasm_current_state = state if save_success
110
-
111
- save_success
112
123
  end
113
124
 
114
- def aasm_current_state=(state)
115
- if self.respond_to?(:aasm_write_state_without_persistence) || self.private_methods.include?('aasm_write_state_without_persistence')
116
- aasm_write_state_without_persistence(state)
117
- end
118
- @aasm_current_state = state
119
- end
125
+ def fire_default_callbacks(event, *processed_args)
126
+ event.fire_global_callbacks(
127
+ :before_all_events,
128
+ self,
129
+ *processed_args
130
+ )
120
131
 
121
- def aasm_determine_state_name(state)
122
- case state
123
- when Symbol, String
124
- state
125
- when Proc
126
- state.call(self)
127
- else
128
- raise NotImplementedError, "Unrecognized state-type given. Expected Symbol, String, or Proc."
129
- end
132
+ # new event before callback
133
+ event.fire_callbacks(
134
+ :before,
135
+ self,
136
+ *processed_args
137
+ )
130
138
  end
131
139
 
132
- def aasm_state_object_for_state(name)
133
- obj = self.class.aasm_states.find {|s| s == name}
134
- raise AASM::UndefinedState, "State :#{name} doesn't exist" if obj.nil?
135
- obj
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)
136
143
  end
137
144
 
138
- def aasm_fire_event(name, persist, *args)
139
- old_state = aasm_state_object_for_state(aasm_current_state)
140
- event = self.class.aasm_events[name]
145
+ def aasm_fired(state_machine_name, event, old_state, new_state_name, options, *args)
146
+ persist = options[:persist]
141
147
 
142
- old_state.call_action(:exit, self)
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)
143
150
 
144
- # new event before callback
145
- event.call_action(:before, self)
146
-
147
- new_state_name = event.fire(self, *args)
148
-
149
- unless new_state_name.nil?
150
- new_state = aasm_state_object_for_state(new_state_name)
151
-
152
- # new before_ callbacks
153
- old_state.call_action(:before_exit, self)
154
- new_state.call_action(:before_enter, self)
155
-
156
- new_state.call_action(:enter, self)
157
-
158
- persist_successful = true
159
- if persist
160
- persist_successful = set_aasm_current_state_with_persistence(new_state_name)
161
- event.execute_success_callback(self) if persist_successful
162
- else
163
- self.aasm_current_state = new_state_name
164
- end
151
+ new_state.fire_callbacks(:before_enter, self, *callback_args)
165
152
 
166
- if persist_successful
167
- old_state.call_action(:after_exit, self)
168
- new_state.call_action(:after_enter, self)
169
- event.call_action(:after, self)
153
+ new_state.fire_callbacks(:enter, self, *callback_args) # TODO: remove for AASM 4?
170
154
 
171
- self.aasm_event_fired(name, old_state.name, self.aasm_current_state) if self.respond_to?(:aasm_event_fired)
172
- else
173
- self.aasm_event_failed(name, old_state.name) if self.respond_to?(:aasm_event_failed)
155
+ persist_successful = true
156
+ if persist
157
+ persist_successful = aasm(state_machine_name).set_current_state_with_persistence(new_state_name)
158
+ if persist_successful
159
+ yield if block_given?
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)
174
163
  end
164
+ else
165
+ aasm(state_machine_name).current_state = new_state_name
166
+ yield if block_given?
167
+ end
175
168
 
176
- persist_successful
169
+ binding_event = event.options[:binding_event]
170
+ if binding_event
171
+ __send__("#{binding_event}#{'!' if persist}")
172
+ end
173
+
174
+ if persist_successful
175
+ old_state.fire_callbacks(:after_exit, self, *callback_args)
176
+ new_state.fire_callbacks(:after_enter, self, *callback_args)
177
+ event.fire_callbacks(
178
+ :after,
179
+ self,
180
+ *process_args(event, old_state.name, *args)
181
+ )
182
+ event.fire_global_callbacks(
183
+ :after_all_events,
184
+ self,
185
+ *process_args(event, old_state.name, *args)
186
+ )
187
+
188
+ self.aasm_event_fired(event.name, old_state.name, aasm(state_machine_name).current_state) if self.respond_to?(:aasm_event_fired)
177
189
  else
178
- if self.respond_to?(:aasm_event_failed)
179
- self.aasm_event_failed(name, old_state.name)
180
- end
190
+ self.aasm_event_failed(event.name, old_state.name) if self.respond_to?(:aasm_event_failed)
191
+ end
181
192
 
193
+ persist_successful
194
+ end
195
+
196
+ def aasm_failed(state_machine_name, event_name, old_state, failures = [])
197
+ if self.respond_to?(:aasm_event_failed)
198
+ self.aasm_event_failed(event_name, old_state.name)
199
+ end
200
+
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)
203
+ else
182
204
  false
183
205
  end
184
206
  end
207
+
185
208
  end
data/lib/aasm/base.rb ADDED
@@ -0,0 +1,290 @@
1
+ require 'logger'
2
+
3
+ module AASM
4
+ class Base
5
+
6
+ attr_reader :klass, :state_machine
7
+
8
+ def initialize(klass, name, state_machine, options={}, &block)
9
+ @klass = klass
10
+ @name = name
11
+ # @state_machine = klass.aasm(@name).state_machine
12
+ @state_machine = state_machine
13
+ @state_machine.config.column ||= (options[:column] || default_column).to_sym
14
+ # @state_machine.config.column = options[:column].to_sym if options[:column] # master
15
+ @options = options
16
+
17
+ # let's cry if the transition is invalid
18
+ configure :whiny_transitions, true
19
+
20
+ # create named scopes for each state
21
+ configure :create_scopes, true
22
+
23
+ # don't store any new state if the model is invalid (in ActiveRecord)
24
+ configure :skip_validation_on_save, false
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
+
32
+ # use requires_new for nested transactions (in ActiveRecord)
33
+ configure :requires_new_transaction, true
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
+
43
+ # set to true to forbid direct assignment of aasm_state column (in ActiveRecord)
44
+ configure :no_direct_assignment, false
45
+
46
+ # allow a AASM::Base sub-class to be used for state machine
47
+ configure :with_klass, AASM::Base
48
+
49
+ configure :enum, nil
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
+
60
+ # make sure to raise an error if no_direct_assignment is enabled
61
+ # and attribute is directly assigned though
62
+ setup_no_direct_assignment(@name)
63
+ end
64
+
65
+ # This method is both a getter and a setter
66
+ def attribute_name(column_name=nil)
67
+ if column_name
68
+ @state_machine.config.column = column_name.to_sym
69
+ else
70
+ @state_machine.config.column ||= :aasm_state
71
+ end
72
+ @state_machine.config.column
73
+ end
74
+
75
+ def initial_state(new_initial_state=nil)
76
+ if new_initial_state
77
+ @state_machine.initial_state = new_initial_state
78
+ else
79
+ @state_machine.initial_state
80
+ end
81
+ end
82
+
83
+ # define a state
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)
94
+
95
+ aasm_name = @name.to_sym
96
+ state = name.to_sym
97
+
98
+ method_name = namespace? ? "#{namespace}_#{name}" : name
99
+ safely_define_method klass, "#{method_name}?", -> do
100
+ aasm(aasm_name).current_state == state
101
+ end
102
+
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
107
+ end
108
+ end
109
+
110
+ # define an event
111
+ def event(name, options={}, &block)
112
+ @state_machine.add_event(name, options, &block)
113
+
114
+ aasm_name = @name.to_sym
115
+ event = name.to_sym
116
+
117
+ # an addition over standard aasm so that, before firing an event, you can ask
118
+ # may_event? and get back a boolean that tells you whether the guard method
119
+ # on the transition will let this happen.
120
+ safely_define_method klass, "may_#{name}?", ->(*args) do
121
+ aasm(aasm_name).may_fire_event?(event, *args)
122
+ end
123
+
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
142
+
143
+ end
144
+
145
+ def after_all_transitions(*callbacks, &block)
146
+ @state_machine.add_global_callbacks(:after_all_transitions, *callbacks, &block)
147
+ end
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
+
173
+ def states
174
+ @state_machine.states
175
+ end
176
+
177
+ def events
178
+ @state_machine.events.values
179
+ end
180
+
181
+ # aasm.event(:event_name).human?
182
+ def human_event_name(event) # event_name?
183
+ AASM::Localizer.new.human_event_name(klass, event)
184
+ end
185
+
186
+ def states_for_select
187
+ states.map { |state| state.for_select }
188
+ end
189
+
190
+ def from_states_for_state(state, options={})
191
+ if options[:transition]
192
+ @state_machine.events[options[:transition]].transitions_to_state(state).flatten.map(&:from).flatten
193
+ else
194
+
195
+ events.map {|e| e.transitions_to_state(state)}.flatten.map(&:from).flatten
196
+ end
197
+ end
198
+
199
+ private
200
+
201
+ def default_column
202
+ @name.to_sym == :default ? :aasm_state : @name.to_sym
203
+ end
204
+
205
+ def configure(key, default_value)
206
+ if @options.key?(key)
207
+ @state_machine.config.send("#{key}=", @options[key])
208
+ elsif @state_machine.config.send(key).nil?
209
+ @state_machine.config.send("#{key}=", default_value)
210
+ end
211
+ end
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)
227
+ end
228
+
229
+ def namespace?
230
+ !!@state_machine.config.namespace
231
+ end
232
+
233
+ def namespace
234
+ if @state_machine.config.namespace == true
235
+ @name
236
+ else
237
+ @state_machine.config.namespace
238
+ end
239
+ end
240
+
241
+ def interpret_state_args(args)
242
+ if args.last.is_a?(Hash) && args.size == 2
243
+ [[args.first], args.last]
244
+ elsif args.size > 0
245
+ [args, {}]
246
+ else
247
+ raise "count not parse states: #{args}"
248
+ end
249
+ end
250
+
251
+ def skip_instance_level_validation(event, name, aasm_name, klass)
252
+ # Overrides the skip_validation config for an instance (If skip validation is set to false in original config) and
253
+ # restores it back to the original value after the event is fired.
254
+ safely_define_method klass, "#{name}_without_validation!", ->(*args, &block) do
255
+ original_config = AASM::StateMachineStore.fetch(self.class, true).machine(aasm_name).config.skip_validation_on_save
256
+ begin
257
+ AASM::StateMachineStore.fetch(self.class, true).machine(aasm_name).config.skip_validation_on_save = true unless original_config
258
+ aasm(aasm_name).current_event = :"#{name}!"
259
+ aasm_fire_event(aasm_name, event, {:persist => true}, *args, &block)
260
+ ensure
261
+ AASM::StateMachineStore.fetch(self.class, true).machine(aasm_name).config.skip_validation_on_save = original_config
262
+ end
263
+ end
264
+ end
265
+
266
+ def setup_timestamps(aasm_name)
267
+ return unless @state_machine.config.timestamps
268
+
269
+ after_all_transitions do
270
+ if self.class.aasm(:"#{aasm_name}").state_machine.config.timestamps
271
+ ts_setter = "#{aasm(aasm_name).to_state}_at="
272
+ respond_to?(ts_setter) && send(ts_setter, ::Time.now)
273
+ end
274
+ end
275
+ end
276
+
277
+ def setup_no_direct_assignment(aasm_name)
278
+ return unless @state_machine.config.no_direct_assignment
279
+
280
+ @klass.send(:define_method, "#{@state_machine.config.column}=") do |state_name|
281
+ if self.class.aasm(:"#{aasm_name}").state_machine.config.no_direct_assignment
282
+ raise AASM::NoDirectAssignmentError.new('direct assignment of AASM column has been disabled (see AASM configuration for this class)')
283
+ else
284
+ super(state_name)
285
+ end
286
+ end
287
+ end
288
+
289
+ end
290
+ end
@@ -0,0 +1,48 @@
1
+ module AASM
2
+ class Configuration
3
+ # for all persistence layers: which database column to use?
4
+ attr_accessor :column
5
+
6
+ # let's cry if the transition is invalid
7
+ attr_accessor :whiny_transitions
8
+
9
+ # for all persistence layers: create named scopes for each state
10
+ attr_accessor :create_scopes
11
+
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
16
+ attr_accessor :skip_validation_on_save
17
+
18
+ # for ActiveRecord: use transactions
19
+ attr_accessor :use_transactions
20
+
21
+ # for ActiveRecord: use requires_new for nested transactions?
22
+ attr_accessor :requires_new_transaction
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
+
30
+ # forbid direct assignment in aasm_state column (in ActiveRecord)
31
+ attr_accessor :no_direct_assignment
32
+
33
+ # allow a AASM::Base sub-class to be used for state machine
34
+ attr_accessor :with_klass
35
+
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
47
+ end
48
+ end