state_machines 0.5.0 → 0.100.4

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 (486) hide show
  1. checksums.yaml +5 -5
  2. data/LICENSE.txt +1 -1
  3. data/README.md +443 -22
  4. data/lib/state_machines/async_mode/async_event_extensions.rb +49 -0
  5. data/lib/state_machines/async_mode/async_events.rb +282 -0
  6. data/lib/state_machines/async_mode/async_machine.rb +60 -0
  7. data/lib/state_machines/async_mode/async_transition_collection.rb +141 -0
  8. data/lib/state_machines/async_mode/thread_safe_state.rb +47 -0
  9. data/lib/state_machines/async_mode.rb +64 -0
  10. data/lib/state_machines/branch.rb +148 -85
  11. data/lib/state_machines/callback.rb +35 -32
  12. data/lib/state_machines/core.rb +3 -3
  13. data/lib/state_machines/core_ext/class/state_machine.rb +2 -0
  14. data/lib/state_machines/core_ext.rb +2 -0
  15. data/lib/state_machines/error.rb +7 -4
  16. data/lib/state_machines/eval_helpers.rb +195 -39
  17. data/lib/state_machines/event.rb +77 -58
  18. data/lib/state_machines/event_collection.rb +48 -38
  19. data/lib/state_machines/extensions.rb +6 -4
  20. data/lib/state_machines/helper_module.rb +4 -2
  21. data/lib/state_machines/integrations/base.rb +9 -1
  22. data/lib/state_machines/integrations.rb +19 -20
  23. data/lib/state_machines/machine/action_hooks.rb +53 -0
  24. data/lib/state_machines/machine/async_extensions.rb +88 -0
  25. data/lib/state_machines/machine/callbacks.rb +59 -0
  26. data/lib/state_machines/machine/class_methods.rb +95 -0
  27. data/lib/state_machines/machine/configuration.rb +136 -0
  28. data/lib/state_machines/machine/event_methods.rb +59 -0
  29. data/lib/state_machines/machine/helper_generators.rb +125 -0
  30. data/lib/state_machines/machine/integration.rb +70 -0
  31. data/lib/state_machines/machine/parsing.rb +77 -0
  32. data/lib/state_machines/machine/rendering.rb +17 -0
  33. data/lib/state_machines/machine/scoping.rb +44 -0
  34. data/lib/state_machines/machine/state_methods.rb +101 -0
  35. data/lib/state_machines/machine/utilities.rb +85 -0
  36. data/lib/state_machines/machine/validation.rb +39 -0
  37. data/lib/state_machines/machine.rb +423 -1011
  38. data/lib/state_machines/machine_collection.rb +28 -19
  39. data/lib/state_machines/macro_methods.rb +104 -102
  40. data/lib/state_machines/matcher.rb +31 -28
  41. data/lib/state_machines/matcher_helpers.rb +14 -12
  42. data/lib/state_machines/node_collection.rb +36 -29
  43. data/lib/state_machines/options_validator.rb +72 -0
  44. data/lib/state_machines/path.rb +60 -57
  45. data/lib/state_machines/path_collection.rb +39 -36
  46. data/lib/state_machines/state.rb +84 -47
  47. data/lib/state_machines/state_collection.rb +22 -19
  48. data/lib/state_machines/state_context.rb +40 -39
  49. data/lib/state_machines/stdio_renderer.rb +74 -0
  50. data/lib/state_machines/syntax_validator.rb +57 -0
  51. data/lib/state_machines/test_helper.rb +895 -0
  52. data/lib/state_machines/transition.rb +386 -204
  53. data/lib/state_machines/transition_collection.rb +208 -171
  54. data/lib/state_machines/version.rb +3 -1
  55. data/lib/state_machines.rb +4 -1
  56. metadata +39 -446
  57. data/.gitignore +0 -21
  58. data/.rspec +0 -3
  59. data/.ruby-gemset +0 -1
  60. data/.ruby-version +0 -1
  61. data/.travis.yml +0 -16
  62. data/Changelog.md +0 -22
  63. data/Contributors.md +0 -39
  64. data/Gemfile +0 -8
  65. data/Rakefile +0 -12
  66. data/Testing.md +0 -0
  67. data/lib/state_machines/assertions.rb +0 -40
  68. data/state_machines.gemspec +0 -22
  69. data/test/files/integrations/event_on_failure_integration.rb +0 -10
  70. data/test/files/integrations/vehicle.rb +0 -7
  71. data/test/files/models/auto_shop.rb +0 -31
  72. data/test/files/models/car.rb +0 -21
  73. data/test/files/models/driver.rb +0 -13
  74. data/test/files/models/model_base.rb +0 -6
  75. data/test/files/models/motorcycle.rb +0 -16
  76. data/test/files/models/traffic_light.rb +0 -47
  77. data/test/files/models/vehicle.rb +0 -127
  78. data/test/files/node.rb +0 -5
  79. data/test/files/switch.rb +0 -15
  80. data/test/functional/auto_shop_available_test.rb +0 -20
  81. data/test/functional/auto_shop_busy_test.rb +0 -25
  82. data/test/functional/car_backing_up_test.rb +0 -45
  83. data/test/functional/car_test.rb +0 -49
  84. data/test/functional/driver_default_nonstandard_test.rb +0 -13
  85. data/test/functional/motorcycle_test.rb +0 -52
  86. data/test/functional/traffic_light_caution_test.rb +0 -17
  87. data/test/functional/traffic_light_proceed_test.rb +0 -17
  88. data/test/functional/traffic_light_stop_test.rb +0 -26
  89. data/test/functional/vehicle_first_gear_test.rb +0 -42
  90. data/test/functional/vehicle_idling_test.rb +0 -59
  91. data/test/functional/vehicle_locked_test.rb +0 -29
  92. data/test/functional/vehicle_parked_test.rb +0 -53
  93. data/test/functional/vehicle_repaired_test.rb +0 -20
  94. data/test/functional/vehicle_second_gear_test.rb +0 -42
  95. data/test/functional/vehicle_stalled_test.rb +0 -65
  96. data/test/functional/vehicle_test.rb +0 -20
  97. data/test/functional/vehicle_third_gear_test.rb +0 -42
  98. data/test/functional/vehicle_unsaved_test.rb +0 -181
  99. data/test/functional/vehicle_with_event_attributes_test.rb +0 -30
  100. data/test/functional/vehicle_with_parallel_events_test.rb +0 -36
  101. data/test/test_helper.rb +0 -15
  102. data/test/unit/assertions/assert_exclusive_keys_test.rb +0 -22
  103. data/test/unit/assertions/assert_valid_key_test.rb +0 -12
  104. data/test/unit/branch/branch_test.rb +0 -28
  105. data/test/unit/branch/branch_with_conflicting_conditionals_test.rb +0 -27
  106. data/test/unit/branch/branch_with_conflicting_from_requirements_test.rb +0 -8
  107. data/test/unit/branch/branch_with_conflicting_on_requirements_test.rb +0 -8
  108. data/test/unit/branch/branch_with_conflicting_to_requirements_test.rb +0 -8
  109. data/test/unit/branch/branch_with_different_requirements_test.rb +0 -41
  110. data/test/unit/branch/branch_with_except_from_matcher_requirement_test.rb +0 -8
  111. data/test/unit/branch/branch_with_except_from_requirement_test.rb +0 -36
  112. data/test/unit/branch/branch_with_except_on_matcher_requirement_test.rb +0 -8
  113. data/test/unit/branch/branch_with_except_on_requirement_test.rb +0 -36
  114. data/test/unit/branch/branch_with_except_to_matcher_requirement_test.rb +0 -8
  115. data/test/unit/branch/branch_with_except_to_requirement_test.rb +0 -36
  116. data/test/unit/branch/branch_with_from_matcher_requirement_test.rb +0 -20
  117. data/test/unit/branch/branch_with_from_requirement_test.rb +0 -45
  118. data/test/unit/branch/branch_with_if_conditional_test.rb +0 -27
  119. data/test/unit/branch/branch_with_implicit_and_explicit_requirements_test.rb +0 -23
  120. data/test/unit/branch/branch_with_implicit_from_requirement_matcher_test.rb +0 -20
  121. data/test/unit/branch/branch_with_implicit_requirement_test.rb +0 -20
  122. data/test/unit/branch/branch_with_implicit_to_requirement_matcher_test.rb +0 -16
  123. data/test/unit/branch/branch_with_multiple_except_from_requirements_test.rb +0 -20
  124. data/test/unit/branch/branch_with_multiple_except_on_requirements_test.rb +0 -16
  125. data/test/unit/branch/branch_with_multiple_except_to_requirements_test.rb +0 -20
  126. data/test/unit/branch/branch_with_multiple_from_requirements_test.rb +0 -16
  127. data/test/unit/branch/branch_with_multiple_if_conditionals_test.rb +0 -20
  128. data/test/unit/branch/branch_with_multiple_implicit_requirements_test.rb +0 -53
  129. data/test/unit/branch/branch_with_multiple_to_requirements_test.rb +0 -20
  130. data/test/unit/branch/branch_with_multiple_unless_conditionals_test.rb +0 -20
  131. data/test/unit/branch/branch_with_nil_requirements_test.rb +0 -28
  132. data/test/unit/branch/branch_with_no_requirements_test.rb +0 -36
  133. data/test/unit/branch/branch_with_on_matcher_requirement_test.rb +0 -16
  134. data/test/unit/branch/branch_with_on_requirement_test.rb +0 -45
  135. data/test/unit/branch/branch_with_to_matcher_requirement_test.rb +0 -20
  136. data/test/unit/branch/branch_with_to_requirement_test.rb +0 -45
  137. data/test/unit/branch/branch_with_unless_conditional_test.rb +0 -27
  138. data/test/unit/branch/branch_without_guards_test.rb +0 -27
  139. data/test/unit/callback/callback_by_default_test.rb +0 -25
  140. data/test/unit/callback/callback_test.rb +0 -53
  141. data/test/unit/callback/callback_with_application_bound_object_test.rb +0 -23
  142. data/test/unit/callback/callback_with_application_terminator_test.rb +0 -24
  143. data/test/unit/callback/callback_with_arguments_test.rb +0 -14
  144. data/test/unit/callback/callback_with_around_type_and_arguments_test.rb +0 -25
  145. data/test/unit/callback/callback_with_around_type_and_block_test.rb +0 -44
  146. data/test/unit/callback/callback_with_around_type_and_bound_method_test.rb +0 -23
  147. data/test/unit/callback/callback_with_around_type_and_multiple_methods_test.rb +0 -93
  148. data/test/unit/callback/callback_with_around_type_and_terminator_test.rb +0 -17
  149. data/test/unit/callback/callback_with_block_test.rb +0 -20
  150. data/test/unit/callback/callback_with_bound_method_and_arguments_test.rb +0 -28
  151. data/test/unit/callback/callback_with_bound_method_test.rb +0 -35
  152. data/test/unit/callback/callback_with_do_method_test.rb +0 -18
  153. data/test/unit/callback/callback_with_explicit_requirements_test.rb +0 -32
  154. data/test/unit/callback/callback_with_if_condition_test.rb +0 -17
  155. data/test/unit/callback/callback_with_implicit_requirements_test.rb +0 -32
  156. data/test/unit/callback/callback_with_method_argument_test.rb +0 -18
  157. data/test/unit/callback/callback_with_mixed_methods_test.rb +0 -31
  158. data/test/unit/callback/callback_with_multiple_bound_methods_test.rb +0 -21
  159. data/test/unit/callback/callback_with_multiple_do_methods_test.rb +0 -29
  160. data/test/unit/callback/callback_with_multiple_method_arguments_test.rb +0 -29
  161. data/test/unit/callback/callback_with_terminator_test.rb +0 -22
  162. data/test/unit/callback/callback_with_unbound_method_test.rb +0 -14
  163. data/test/unit/callback/callback_with_unless_condition_test.rb +0 -17
  164. data/test/unit/callback/callback_without_arguments_test.rb +0 -14
  165. data/test/unit/callback/callback_without_terminator_test.rb +0 -12
  166. data/test/unit/error/error_by_default_test.rb +0 -21
  167. data/test/unit/error/error_with_message_test.rb +0 -23
  168. data/test/unit/eval_helper/eval_helpers_base_test.rb +0 -8
  169. data/test/unit/eval_helper/eval_helpers_proc_block_and_explicit_arguments_test.rb +0 -14
  170. data/test/unit/eval_helper/eval_helpers_proc_block_and_implicit_arguments_test.rb +0 -14
  171. data/test/unit/eval_helper/eval_helpers_proc_test.rb +0 -13
  172. data/test/unit/eval_helper/eval_helpers_proc_with_arguments_test.rb +0 -13
  173. data/test/unit/eval_helper/eval_helpers_proc_with_block_test.rb +0 -13
  174. data/test/unit/eval_helper/eval_helpers_proc_with_block_without_arguments_test.rb +0 -18
  175. data/test/unit/eval_helper/eval_helpers_proc_with_block_without_object_test.rb +0 -14
  176. data/test/unit/eval_helper/eval_helpers_proc_without_arguments_test.rb +0 -19
  177. data/test/unit/eval_helper/eval_helpers_string_test.rb +0 -25
  178. data/test/unit/eval_helper/eval_helpers_string_with_block_test.rb +0 -12
  179. data/test/unit/eval_helper/eval_helpers_symbol_method_missing_test.rb +0 -20
  180. data/test/unit/eval_helper/eval_helpers_symbol_private_test.rb +0 -17
  181. data/test/unit/eval_helper/eval_helpers_symbol_protected_test.rb +0 -17
  182. data/test/unit/eval_helper/eval_helpers_symbol_tainted_method_test.rb +0 -18
  183. data/test/unit/eval_helper/eval_helpers_symbol_test.rb +0 -16
  184. data/test/unit/eval_helper/eval_helpers_symbol_with_arguments_and_block_test.rb +0 -16
  185. data/test/unit/eval_helper/eval_helpers_symbol_with_arguments_test.rb +0 -16
  186. data/test/unit/eval_helper/eval_helpers_symbol_with_block_test.rb +0 -16
  187. data/test/unit/eval_helper/eval_helpers_test.rb +0 -13
  188. data/test/unit/event/event_after_being_copied_test.rb +0 -17
  189. data/test/unit/event/event_by_default_test.rb +0 -60
  190. data/test/unit/event/event_context_test.rb +0 -16
  191. data/test/unit/event/event_on_failure_test.rb +0 -44
  192. data/test/unit/event/event_test.rb +0 -34
  193. data/test/unit/event/event_transitions_test.rb +0 -62
  194. data/test/unit/event/event_with_conflicting_helpers_after_definition_test.rb +0 -79
  195. data/test/unit/event/event_with_conflicting_helpers_before_definition_test.rb +0 -58
  196. data/test/unit/event/event_with_conflicting_machine_test.rb +0 -48
  197. data/test/unit/event/event_with_dynamic_human_name_test.rb +0 -26
  198. data/test/unit/event/event_with_human_name_test.rb +0 -13
  199. data/test/unit/event/event_with_invalid_current_state_test.rb +0 -30
  200. data/test/unit/event/event_with_machine_action_test.rb +0 -33
  201. data/test/unit/event/event_with_marshalling_test.rb +0 -47
  202. data/test/unit/event/event_with_matching_disabled_transitions_test.rb +0 -115
  203. data/test/unit/event/event_with_matching_enabled_transitions_test.rb +0 -75
  204. data/test/unit/event/event_with_multiple_transitions_test.rb +0 -61
  205. data/test/unit/event/event_with_namespace_test.rb +0 -34
  206. data/test/unit/event/event_with_transition_with_blacklisted_to_state_test.rb +0 -60
  207. data/test/unit/event/event_with_transition_with_loopback_state_test.rb +0 -36
  208. data/test/unit/event/event_with_transition_with_nil_to_state_test.rb +0 -36
  209. data/test/unit/event/event_with_transition_with_whitelisted_to_state_test.rb +0 -51
  210. data/test/unit/event/event_with_transition_without_to_state_test.rb +0 -36
  211. data/test/unit/event/event_with_transitions_test.rb +0 -32
  212. data/test/unit/event/event_without_matching_transitions_test.rb +0 -41
  213. data/test/unit/event/event_without_transitions_test.rb +0 -28
  214. data/test/unit/event/invalid_event_test.rb +0 -20
  215. data/test/unit/event_collection/event_collection_attribute_with_machine_action_test.rb +0 -62
  216. data/test/unit/event_collection/event_collection_attribute_with_namespaced_machine_test.rb +0 -36
  217. data/test/unit/event_collection/event_collection_by_default_test.rb +0 -26
  218. data/test/unit/event_collection/event_collection_test.rb +0 -39
  219. data/test/unit/event_collection/event_collection_with_custom_machine_attribute_test.rb +0 -31
  220. data/test/unit/event_collection/event_collection_with_events_with_transitions_test.rb +0 -76
  221. data/test/unit/event_collection/event_collection_with_multiple_events_test.rb +0 -27
  222. data/test/unit/event_collection/event_collection_with_validations_test.rb +0 -74
  223. data/test/unit/event_collection/event_collection_without_machine_action_test.rb +0 -18
  224. data/test/unit/event_collection/event_string_collection_test.rb +0 -31
  225. data/test/unit/helper_module_test.rb +0 -17
  226. data/test/unit/integrations/integration_finder_test.rb +0 -16
  227. data/test/unit/integrations/integration_matcher_test.rb +0 -29
  228. data/test/unit/invalid_transition/invalid_parallel_transition_test.rb +0 -18
  229. data/test/unit/invalid_transition/invalid_transition_test.rb +0 -47
  230. data/test/unit/invalid_transition/invalid_transition_with_integration_test.rb +0 -45
  231. data/test/unit/invalid_transition/invalid_transition_with_namespace_test.rb +0 -32
  232. data/test/unit/machine/machine_after_being_copied_test.rb +0 -62
  233. data/test/unit/machine/machine_after_changing_initial_state.rb +0 -28
  234. data/test/unit/machine/machine_after_changing_owner_class_test.rb +0 -31
  235. data/test/unit/machine/machine_by_default_test.rb +0 -160
  236. data/test/unit/machine/machine_finder_custom_options_test.rb +0 -17
  237. data/test/unit/machine/machine_finder_with_existing_machine_on_superclass_test.rb +0 -85
  238. data/test/unit/machine/machine_finder_with_existing_on_same_class_test.rb +0 -23
  239. data/test/unit/machine/machine_finder_without_existing_machine_test.rb +0 -25
  240. data/test/unit/machine/machine_persistence_test.rb +0 -52
  241. data/test/unit/machine/machine_state_initialization_test.rb +0 -56
  242. data/test/unit/machine/machine_test.rb +0 -30
  243. data/test/unit/machine/machine_with_action_already_overridden_test.rb +0 -23
  244. data/test/unit/machine/machine_with_action_defined_in_class_test.rb +0 -37
  245. data/test/unit/machine/machine_with_action_defined_in_included_module_test.rb +0 -46
  246. data/test/unit/machine/machine_with_action_defined_in_superclass_test.rb +0 -43
  247. data/test/unit/machine/machine_with_action_undefined_test.rb +0 -33
  248. data/test/unit/machine/machine_with_cached_state_test.rb +0 -20
  249. data/test/unit/machine/machine_with_class_helpers_test.rb +0 -179
  250. data/test/unit/machine/machine_with_conflicting_helpers_after_definition_test.rb +0 -244
  251. data/test/unit/machine/machine_with_conflicting_helpers_before_definition_test.rb +0 -175
  252. data/test/unit/machine/machine_with_custom_action_test.rb +0 -11
  253. data/test/unit/machine/machine_with_custom_attribute_test.rb +0 -103
  254. data/test/unit/machine/machine_with_custom_initialize_test.rb +0 -24
  255. data/test/unit/machine/machine_with_custom_integration_test.rb +0 -72
  256. data/test/unit/machine/machine_with_custom_invalidation_test.rb +0 -39
  257. data/test/unit/machine/machine_with_custom_name_test.rb +0 -57
  258. data/test/unit/machine/machine_with_custom_plural_test.rb +0 -52
  259. data/test/unit/machine/machine_with_dynamic_initial_state_test.rb +0 -65
  260. data/test/unit/machine/machine_with_event_matchers_test.rb +0 -41
  261. data/test/unit/machine/machine_with_events_test.rb +0 -52
  262. data/test/unit/machine/machine_with_events_with_custom_human_names_test.rb +0 -18
  263. data/test/unit/machine/machine_with_events_with_transitions_test.rb +0 -37
  264. data/test/unit/machine/machine_with_existing_event_test.rb +0 -17
  265. data/test/unit/machine/machine_with_existing_machines_on_owner_class_test.rb +0 -20
  266. data/test/unit/machine/machine_with_existing_machines_with_same_attributes_on_owner_class_test.rb +0 -71
  267. data/test/unit/machine/machine_with_existing_machines_with_same_attributes_on_owner_subclass_test.rb +0 -31
  268. data/test/unit/machine/machine_with_existing_state_test.rb +0 -27
  269. data/test/unit/machine/machine_with_failure_callbacks_test.rb +0 -48
  270. data/test/unit/machine/machine_with_helpers_test.rb +0 -14
  271. data/test/unit/machine/machine_with_initial_state_with_value_and_owner_default.rb +0 -25
  272. data/test/unit/machine/machine_with_initialize_and_super_test.rb +0 -17
  273. data/test/unit/machine/machine_with_initialize_arguments_and_block_test.rb +0 -31
  274. data/test/unit/machine/machine_with_initialize_without_super_test.rb +0 -17
  275. data/test/unit/machine/machine_with_instance_helpers_test.rb +0 -179
  276. data/test/unit/machine/machine_with_integration_test.rb +0 -72
  277. data/test/unit/machine/machine_with_multiple_events_test.rb +0 -32
  278. data/test/unit/machine/machine_with_namespace_test.rb +0 -48
  279. data/test/unit/machine/machine_with_nil_action_test.rb +0 -27
  280. data/test/unit/machine/machine_with_other_states.rb +0 -22
  281. data/test/unit/machine/machine_with_owner_subclass_test.rb +0 -18
  282. data/test/unit/machine/machine_with_paths_test.rb +0 -25
  283. data/test/unit/machine/machine_with_private_action_test.rb +0 -43
  284. data/test/unit/machine/machine_with_state_matchers_test.rb +0 -41
  285. data/test/unit/machine/machine_with_state_with_matchers_test.rb +0 -19
  286. data/test/unit/machine/machine_with_states_test.rb +0 -55
  287. data/test/unit/machine/machine_with_states_with_behaviors_test.rb +0 -23
  288. data/test/unit/machine/machine_with_states_with_custom_human_names_test.rb +0 -18
  289. data/test/unit/machine/machine_with_states_with_custom_values_test.rb +0 -21
  290. data/test/unit/machine/machine_with_states_with_runtime_dependencies_test.rb +0 -19
  291. data/test/unit/machine/machine_with_static_initial_state_test.rb +0 -49
  292. data/test/unit/machine/machine_with_superclass_conflicting_helpers_after_definition_test.rb +0 -36
  293. data/test/unit/machine/machine_with_transition_callbacks_test.rb +0 -144
  294. data/test/unit/machine/machine_with_transitions_test.rb +0 -87
  295. data/test/unit/machine/machine_without_initialization_test.rb +0 -31
  296. data/test/unit/machine/machine_without_initialize_test.rb +0 -14
  297. data/test/unit/machine/machine_without_integration_test.rb +0 -31
  298. data/test/unit/machine_collection/machine_collection_by_default_test.rb +0 -11
  299. data/test/unit/machine_collection/machine_collection_fire_test.rb +0 -80
  300. data/test/unit/machine_collection/machine_collection_fire_with_transactions_test.rb +0 -54
  301. data/test/unit/machine_collection/machine_collection_fire_with_validations_test.rb +0 -76
  302. data/test/unit/machine_collection/machine_collection_state_initialization_test.rb +0 -111
  303. data/test/unit/machine_collection/machine_collection_transitions_with_blank_events_test.rb +0 -25
  304. data/test/unit/machine_collection/machine_collection_transitions_with_custom_options_test.rb +0 -20
  305. data/test/unit/machine_collection/machine_collection_transitions_with_different_actions_test.rb +0 -26
  306. data/test/unit/machine_collection/machine_collection_transitions_with_exisiting_transitions_test.rb +0 -25
  307. data/test/unit/machine_collection/machine_collection_transitions_with_invalid_events_test.rb +0 -25
  308. data/test/unit/machine_collection/machine_collection_transitions_with_same_actions_test.rb +0 -31
  309. data/test/unit/machine_collection/machine_collection_transitions_with_transition_test.rb +0 -26
  310. data/test/unit/machine_collection/machine_collection_transitions_without_events_test.rb +0 -25
  311. data/test/unit/machine_collection/machine_collection_transitions_without_transition_test.rb +0 -27
  312. data/test/unit/matcher/all_matcher_test.rb +0 -29
  313. data/test/unit/matcher/blacklist_matcher_test.rb +0 -30
  314. data/test/unit/matcher/loopback_matcher_test.rb +0 -27
  315. data/test/unit/matcher/matcher_by_default_test.rb +0 -15
  316. data/test/unit/matcher/matcher_with_multiple_values_test.rb +0 -15
  317. data/test/unit/matcher/matcher_with_value_test.rb +0 -15
  318. data/test/unit/matcher/whitelist_matcher_test.rb +0 -30
  319. data/test/unit/matcher_helpers/matcher_helpers_all_test.rb +0 -14
  320. data/test/unit/matcher_helpers/matcher_helpers_any_test.rb +0 -14
  321. data/test/unit/matcher_helpers/matcher_helpers_same_test.rb +0 -13
  322. data/test/unit/node_collection/node_collection_after_being_copied_test.rb +0 -46
  323. data/test/unit/node_collection/node_collection_after_update_test.rb +0 -36
  324. data/test/unit/node_collection/node_collection_by_default_test.rb +0 -22
  325. data/test/unit/node_collection/node_collection_test.rb +0 -23
  326. data/test/unit/node_collection/node_collection_with_indices_test.rb +0 -42
  327. data/test/unit/node_collection/node_collection_with_matcher_contexts_test.rb +0 -25
  328. data/test/unit/node_collection/node_collection_with_nodes_test.rb +0 -46
  329. data/test/unit/node_collection/node_collection_with_numeric_index_test.rb +0 -24
  330. data/test/unit/node_collection/node_collection_with_postdefined_contexts_test.rb +0 -22
  331. data/test/unit/node_collection/node_collection_with_predefined_contexts_test.rb +0 -23
  332. data/test/unit/node_collection/node_collection_with_string_index_test.rb +0 -20
  333. data/test/unit/node_collection/node_collection_with_symbol_index_test.rb +0 -20
  334. data/test/unit/node_collection/node_collection_without_indices_test.rb +0 -30
  335. data/test/unit/path/path_by_default_test.rb +0 -54
  336. data/test/unit/path/path_test.rb +0 -14
  337. data/test/unit/path/path_with_available_transitions_after_reaching_target_test.rb +0 -40
  338. data/test/unit/path/path_with_available_transitions_test.rb +0 -54
  339. data/test/unit/path/path_with_deep_target_reached_test.rb +0 -50
  340. data/test/unit/path/path_with_deep_target_test.rb +0 -40
  341. data/test/unit/path/path_with_duplicates_test.rb +0 -32
  342. data/test/unit/path/path_with_encountered_transitions_test.rb +0 -34
  343. data/test/unit/path/path_with_guarded_transitions_test.rb +0 -42
  344. data/test/unit/path/path_with_reached_target_test.rb +0 -35
  345. data/test/unit/path/path_with_transitions_test.rb +0 -54
  346. data/test/unit/path/path_with_unreached_target_test.rb +0 -31
  347. data/test/unit/path/path_without_transitions_test.rb +0 -24
  348. data/test/unit/path_collection/path_collection_by_default_test.rb +0 -46
  349. data/test/unit/path_collection/path_collection_test.rb +0 -24
  350. data/test/unit/path_collection/path_collection_with_deep_paths_test.rb +0 -43
  351. data/test/unit/path_collection/path_collection_with_duplicate_nodes_test.rb +0 -31
  352. data/test/unit/path_collection/path_collection_with_from_state_test.rb +0 -27
  353. data/test/unit/path_collection/path_collection_with_paths_test.rb +0 -47
  354. data/test/unit/path_collection/path_collection_with_to_state_test.rb +0 -29
  355. data/test/unit/path_collection/path_with_guarded_paths_test.rb +0 -25
  356. data/test/unit/state/state_after_being_copied_test.rb +0 -19
  357. data/test/unit/state/state_by_default_test.rb +0 -41
  358. data/test/unit/state/state_final_test.rb +0 -28
  359. data/test/unit/state/state_initial_test.rb +0 -13
  360. data/test/unit/state/state_not_final_test.rb +0 -32
  361. data/test/unit/state/state_not_initial_test.rb +0 -13
  362. data/test/unit/state/state_test.rb +0 -44
  363. data/test/unit/state/state_with_cached_lambda_value_test.rb +0 -29
  364. data/test/unit/state/state_with_conflicting_helpers_after_definition_test.rb +0 -38
  365. data/test/unit/state/state_with_conflicting_helpers_before_definition_test.rb +0 -29
  366. data/test/unit/state/state_with_conflicting_machine_name_test.rb +0 -20
  367. data/test/unit/state/state_with_conflicting_machine_test.rb +0 -37
  368. data/test/unit/state/state_with_context_test.rb +0 -60
  369. data/test/unit/state/state_with_dynamic_human_name_test.rb +0 -25
  370. data/test/unit/state/state_with_existing_context_method_test.rb +0 -24
  371. data/test/unit/state/state_with_human_name_test.rb +0 -13
  372. data/test/unit/state/state_with_integer_value_test.rb +0 -32
  373. data/test/unit/state/state_with_invalid_method_call_test.rb +0 -21
  374. data/test/unit/state/state_with_lambda_value_test.rb +0 -37
  375. data/test/unit/state/state_with_matcher_test.rb +0 -18
  376. data/test/unit/state/state_with_multiple_contexts_test.rb +0 -57
  377. data/test/unit/state/state_with_name_test.rb +0 -43
  378. data/test/unit/state/state_with_namespace_test.rb +0 -22
  379. data/test/unit/state/state_with_nil_value_test.rb +0 -35
  380. data/test/unit/state/state_with_redefined_context_method_test.rb +0 -45
  381. data/test/unit/state/state_with_symbolic_value_test.rb +0 -32
  382. data/test/unit/state/state_with_valid_inherited_method_call_for_current_state_test.rb +0 -40
  383. data/test/unit/state/state_with_valid_method_call_for_current_state_test.rb +0 -33
  384. data/test/unit/state/state_with_valid_method_call_for_different_state_test.rb +0 -41
  385. data/test/unit/state/state_without_cached_lambda_value_test.rb +0 -25
  386. data/test/unit/state/state_without_name_test.rb +0 -39
  387. data/test/unit/state_collection/state_collection_by_default_test.rb +0 -21
  388. data/test/unit/state_collection/state_collection_string_test.rb +0 -35
  389. data/test/unit/state_collection/state_collection_test.rb +0 -74
  390. data/test/unit/state_collection/state_collection_with_custom_state_values_test.rb +0 -29
  391. data/test/unit/state_collection/state_collection_with_event_transitions_test.rb +0 -39
  392. data/test/unit/state_collection/state_collection_with_initial_state_test.rb +0 -40
  393. data/test/unit/state_collection/state_collection_with_namespace_test.rb +0 -21
  394. data/test/unit/state_collection/state_collection_with_state_behaviors_test.rb +0 -40
  395. data/test/unit/state_collection/state_collection_with_state_matchers_test.rb +0 -29
  396. data/test/unit/state_collection/state_collection_with_transition_callbacks_test.rb +0 -40
  397. data/test/unit/state_context/state_context_proxy_test.rb +0 -26
  398. data/test/unit/state_context/state_context_proxy_with_if_and_unless_conditions_test.rb +0 -42
  399. data/test/unit/state_context/state_context_proxy_with_if_condition_test.rb +0 -64
  400. data/test/unit/state_context/state_context_proxy_with_multiple_if_conditions_test.rb +0 -32
  401. data/test/unit/state_context/state_context_proxy_with_multiple_unless_conditions_test.rb +0 -32
  402. data/test/unit/state_context/state_context_proxy_with_unless_condition_test.rb +0 -64
  403. data/test/unit/state_context/state_context_proxy_without_conditions_test.rb +0 -31
  404. data/test/unit/state_context/state_context_test.rb +0 -28
  405. data/test/unit/state_context/state_context_transition_test.rb +0 -104
  406. data/test/unit/state_context/state_context_with_matching_transition_test.rb +0 -27
  407. data/test/unit/state_machine/state_machine_by_default_test.rb +0 -12
  408. data/test/unit/state_machine/state_machine_test.rb +0 -20
  409. data/test/unit/transition/transition_after_being_performed_test.rb +0 -48
  410. data/test/unit/transition/transition_after_being_persisted_test.rb +0 -46
  411. data/test/unit/transition/transition_after_being_rolled_back_test.rb +0 -35
  412. data/test/unit/transition/transition_equality_test.rb +0 -52
  413. data/test/unit/transition/transition_loopback_test.rb +0 -18
  414. data/test/unit/transition/transition_test.rb +0 -96
  415. data/test/unit/transition/transition_transient_test.rb +0 -20
  416. data/test/unit/transition/transition_with_action_test.rb +0 -27
  417. data/test/unit/transition/transition_with_after_callbacks_skipped_test.rb +0 -127
  418. data/test/unit/transition/transition_with_after_callbacks_test.rb +0 -93
  419. data/test/unit/transition/transition_with_around_callbacks_test.rb +0 -141
  420. data/test/unit/transition/transition_with_before_callbacks_skipped_test.rb +0 -30
  421. data/test/unit/transition/transition_with_before_callbacks_test.rb +0 -104
  422. data/test/unit/transition/transition_with_custom_machine_attribute_test.rb +0 -28
  423. data/test/unit/transition/transition_with_different_states_test.rb +0 -18
  424. data/test/unit/transition/transition_with_dynamic_to_value_test.rb +0 -19
  425. data/test/unit/transition/transition_with_failure_callbacks_test.rb +0 -84
  426. data/test/unit/transition/transition_with_invalid_nodes_test.rb +0 -29
  427. data/test/unit/transition/transition_with_mixed_callbacks_test.rb +0 -105
  428. data/test/unit/transition/transition_with_multiple_after_callbacks_test.rb +0 -40
  429. data/test/unit/transition/transition_with_multiple_around_callbacks_test.rb +0 -114
  430. data/test/unit/transition/transition_with_multiple_before_callbacks_test.rb +0 -40
  431. data/test/unit/transition/transition_with_multiple_failure_callbacks_test.rb +0 -40
  432. data/test/unit/transition/transition_with_namespace_test.rb +0 -47
  433. data/test/unit/transition/transition_with_perform_arguments_test.rb +0 -35
  434. data/test/unit/transition/transition_with_transactions_test.rb +0 -42
  435. data/test/unit/transition/transition_without_callbacks_test.rb +0 -33
  436. data/test/unit/transition/transition_without_reading_state_test.rb +0 -22
  437. data/test/unit/transition/transition_without_running_action_test.rb +0 -47
  438. data/test/unit/transition_collection/attribute_transition_collection_by_default_test.rb +0 -23
  439. data/test/unit/transition_collection/attribute_transition_collection_marshalling_test.rb +0 -64
  440. data/test/unit/transition_collection/attribute_transition_collection_with_action_error_test.rb +0 -44
  441. data/test/unit/transition_collection/attribute_transition_collection_with_action_failed_test.rb +0 -44
  442. data/test/unit/transition_collection/attribute_transition_collection_with_after_callback_error_test.rb +0 -32
  443. data/test/unit/transition_collection/attribute_transition_collection_with_after_callback_halt_test.rb +0 -33
  444. data/test/unit/transition_collection/attribute_transition_collection_with_around_after_yield_callback_error_test.rb +0 -32
  445. data/test/unit/transition_collection/attribute_transition_collection_with_around_callback_after_yield_error_test.rb +0 -32
  446. data/test/unit/transition_collection/attribute_transition_collection_with_around_callback_after_yield_halt_test.rb +0 -33
  447. data/test/unit/transition_collection/attribute_transition_collection_with_around_callback_before_yield_halt_test.rb +0 -33
  448. data/test/unit/transition_collection/attribute_transition_collection_with_before_callback_error_test.rb +0 -32
  449. data/test/unit/transition_collection/attribute_transition_collection_with_before_callback_halt_test.rb +0 -33
  450. data/test/unit/transition_collection/attribute_transition_collection_with_callbacks_test.rb +0 -68
  451. data/test/unit/transition_collection/attribute_transition_collection_with_event_transitions_test.rb +0 -41
  452. data/test/unit/transition_collection/attribute_transition_collection_with_events_test.rb +0 -44
  453. data/test/unit/transition_collection/attribute_transition_collection_with_skipped_after_callbacks_test.rb +0 -42
  454. data/test/unit/transition_collection/transition_collection_by_default_test.rb +0 -23
  455. data/test/unit/transition_collection/transition_collection_empty_with_block_test.rb +0 -23
  456. data/test/unit/transition_collection/transition_collection_empty_without_block_test.rb +0 -12
  457. data/test/unit/transition_collection/transition_collection_invalid_test.rb +0 -21
  458. data/test/unit/transition_collection/transition_collection_partial_invalid_test.rb +0 -69
  459. data/test/unit/transition_collection/transition_collection_test.rb +0 -26
  460. data/test/unit/transition_collection/transition_collection_valid_test.rb +0 -57
  461. data/test/unit/transition_collection/transition_collection_with_action_error_test.rb +0 -66
  462. data/test/unit/transition_collection/transition_collection_with_action_failed_test.rb +0 -60
  463. data/test/unit/transition_collection/transition_collection_with_action_hook_and_block_test.rb +0 -17
  464. data/test/unit/transition_collection/transition_collection_with_action_hook_and_skipped_action_test.rb +0 -17
  465. data/test/unit/transition_collection/transition_collection_with_action_hook_and_skipped_after_callbacks_test.rb +0 -37
  466. data/test/unit/transition_collection/transition_collection_with_action_hook_base_test.rb +0 -34
  467. data/test/unit/transition_collection/transition_collection_with_action_hook_error_test.rb +0 -29
  468. data/test/unit/transition_collection/transition_collection_with_action_hook_invalid_test.rb +0 -17
  469. data/test/unit/transition_collection/transition_collection_with_action_hook_multiple_test.rb +0 -79
  470. data/test/unit/transition_collection/transition_collection_with_action_hook_test.rb +0 -45
  471. data/test/unit/transition_collection/transition_collection_with_action_hook_with_different_actions_test.rb +0 -48
  472. data/test/unit/transition_collection/transition_collection_with_action_hook_with_nil_action_test.rb +0 -42
  473. data/test/unit/transition_collection/transition_collection_with_after_callback_halt_test.rb +0 -47
  474. data/test/unit/transition_collection/transition_collection_with_before_callback_halt_test.rb +0 -51
  475. data/test/unit/transition_collection/transition_collection_with_block_test.rb +0 -46
  476. data/test/unit/transition_collection/transition_collection_with_callbacks_test.rb +0 -135
  477. data/test/unit/transition_collection/transition_collection_with_different_actions_test.rb +0 -189
  478. data/test/unit/transition_collection/transition_collection_with_duplicate_actions_test.rb +0 -48
  479. data/test/unit/transition_collection/transition_collection_with_empty_actions_test.rb +0 -41
  480. data/test/unit/transition_collection/transition_collection_with_mixed_actions_test.rb +0 -41
  481. data/test/unit/transition_collection/transition_collection_with_skipped_actions_and_block_test.rb +0 -34
  482. data/test/unit/transition_collection/transition_collection_with_skipped_actions_test.rb +0 -69
  483. data/test/unit/transition_collection/transition_collection_with_skipped_after_callbacks_and_around_callbacks_test.rb +0 -53
  484. data/test/unit/transition_collection/transition_collection_with_skipped_after_callbacks_test.rb +0 -34
  485. data/test/unit/transition_collection/transition_collection_with_transactions_test.rb +0 -65
  486. data/test/unit/transition_collection/transition_collection_without_transactions_test.rb +0 -29
@@ -1,328 +1,347 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'options_validator'
4
+ require_relative 'machine/class_methods'
5
+ require_relative 'machine/utilities'
6
+ require_relative 'machine/parsing'
7
+ require_relative 'machine/validation'
8
+ require_relative 'machine/helper_generators'
9
+ require_relative 'machine/action_hooks'
10
+ require_relative 'machine/scoping'
11
+ require_relative 'machine/configuration'
12
+ require_relative 'machine/state_methods'
13
+ require_relative 'machine/event_methods'
14
+ require_relative 'machine/callbacks'
15
+ require_relative 'machine/rendering'
16
+ require_relative 'machine/integration'
17
+ require_relative 'machine/async_extensions'
18
+ require_relative 'syntax_validator'
19
+
1
20
  module StateMachines
2
21
  # Represents a state machine for a particular attribute. State machines
3
22
  # consist of states, events and a set of transitions that define how the
4
23
  # state changes after a particular event is fired.
5
- #
24
+ #
6
25
  # A state machine will not know all of the possible states for an object
7
26
  # unless they are referenced *somewhere* in the state machine definition.
8
27
  # As a result, any unused states should be defined with the +other_states+
9
28
  # or +state+ helper.
10
- #
29
+ #
11
30
  # == Actions
12
- #
31
+ #
13
32
  # When an action is configured for a state machine, it is invoked when an
14
33
  # object transitions via an event. The success of the event becomes
15
34
  # dependent on the success of the action. If the action is successful, then
16
35
  # the transitioned state remains persisted. However, if the action fails
17
36
  # (by returning false), the transitioned state will be rolled back.
18
- #
37
+ #
19
38
  # For example,
20
- #
39
+ #
21
40
  # class Vehicle
22
41
  # attr_accessor :fail, :saving_state
23
- #
42
+ #
24
43
  # state_machine :initial => :parked, :action => :save do
25
44
  # event :ignite do
26
45
  # transition :parked => :idling
27
46
  # end
28
- #
47
+ #
29
48
  # event :park do
30
49
  # transition :idling => :parked
31
50
  # end
32
51
  # end
33
- #
52
+ #
34
53
  # def save
35
54
  # @saving_state = state
36
55
  # fail != true
37
56
  # end
38
57
  # end
39
- #
58
+ #
40
59
  # vehicle = Vehicle.new # => #<Vehicle:0xb7c27024 @state="parked">
41
60
  # vehicle.save # => true
42
61
  # vehicle.saving_state # => "parked" # The state was "parked" was save was called
43
- #
62
+ #
44
63
  # # Successful event
45
64
  # vehicle.ignite # => true
46
65
  # vehicle.saving_state # => "idling" # The state was "idling" when save was called
47
66
  # vehicle.state # => "idling"
48
- #
67
+ #
49
68
  # # Failed event
50
69
  # vehicle.fail = true
51
70
  # vehicle.park # => false
52
71
  # vehicle.saving_state # => "parked"
53
72
  # vehicle.state # => "idling"
54
- #
73
+ #
55
74
  # As shown, even though the state is set prior to calling the +save+ action
56
75
  # on the object, it will be rolled back to the original state if the action
57
76
  # fails. *Note* that this will also be the case if an exception is raised
58
77
  # while calling the action.
59
- #
78
+ #
60
79
  # === Indirect transitions
61
- #
80
+ #
62
81
  # In addition to the action being run as the _result_ of an event, the action
63
82
  # can also be used to run events itself. For example, using the above as an
64
83
  # example:
65
- #
84
+ #
66
85
  # vehicle = Vehicle.new # => #<Vehicle:0xb7c27024 @state="parked">
67
- #
86
+ #
68
87
  # vehicle.state_event = 'ignite'
69
88
  # vehicle.save # => true
70
89
  # vehicle.state # => "idling"
71
90
  # vehicle.state_event # => nil
72
- #
91
+ #
73
92
  # As can be seen, the +save+ action automatically invokes the event stored in
74
93
  # the +state_event+ attribute (<tt>:ignite</tt> in this case).
75
- #
94
+ #
76
95
  # One important note about using this technique for running transitions is
77
96
  # that if the class in which the state machine is defined *also* defines the
78
97
  # action being invoked (and not a superclass), then it must manually run the
79
98
  # StateMachine hook that checks for event attributes.
80
- #
99
+ #
81
100
  # For example, in ActiveRecord, DataMapper, Mongoid, MongoMapper, and Sequel,
82
101
  # the default action (+save+) is already defined in a base class. As a result,
83
102
  # when a state machine is defined in a model / resource, StateMachine can
84
103
  # automatically hook into the +save+ action.
85
- #
104
+ #
86
105
  # On the other hand, the Vehicle class from above defined its own +save+
87
106
  # method (and there is no +save+ method in its superclass). As a result, it
88
107
  # must be modified like so:
89
- #
108
+ #
90
109
  # def save
91
110
  # self.class.state_machines.transitions(self, :save).perform do
92
111
  # @saving_state = state
93
112
  # fail != true
94
113
  # end
95
114
  # end
96
- #
115
+ #
97
116
  # This will add in the functionality for firing the event stored in the
98
117
  # +state_event+ attribute.
99
- #
118
+ #
100
119
  # == Callbacks
101
- #
120
+ #
102
121
  # Callbacks are supported for hooking before and after every possible
103
122
  # transition in the machine. Each callback is invoked in the order in which
104
123
  # it was defined. See StateMachines::Machine#before_transition and
105
124
  # StateMachines::Machine#after_transition for documentation on how to define
106
125
  # new callbacks.
107
- #
126
+ #
108
127
  # *Note* that callbacks only get executed within the context of an event. As
109
128
  # a result, if a class has an initial state when it's created, any callbacks
110
129
  # that would normally get executed when the object enters that state will
111
130
  # *not* get triggered.
112
- #
131
+ #
113
132
  # For example,
114
- #
133
+ #
115
134
  # class Vehicle
116
- # state_machine :initial => :parked do
135
+ # state_machine initial: :parked do
117
136
  # after_transition all => :parked do
118
137
  # raise ArgumentError
119
138
  # end
120
139
  # ...
121
140
  # end
122
141
  # end
123
- #
142
+ #
124
143
  # vehicle = Vehicle.new # => #<Vehicle id: 1, state: "parked">
125
144
  # vehicle.save # => true (no exception raised)
126
- #
145
+ #
127
146
  # If you need callbacks to get triggered when an object is created, this
128
147
  # should be done by one of the following techniques:
129
148
  # * Use a <tt>before :create</tt> or equivalent hook:
130
- #
149
+ #
131
150
  # class Vehicle
132
151
  # before :create, :track_initial_transition
133
- #
152
+ #
134
153
  # state_machine do
135
154
  # ...
136
155
  # end
137
156
  # end
138
- #
157
+ #
139
158
  # * Set an initial state and use the correct event to create the
140
159
  # object with the proper state, resulting in callbacks being triggered and
141
160
  # the object getting persisted (note that the <tt>:pending</tt> state is
142
161
  # actually stored as nil):
143
- #
162
+ #
144
163
  # class Vehicle
145
- # state_machine :initial => :pending
146
- # after_transition :pending => :parked, :do => :track_initial_transition
147
- #
164
+ # state_machine initial: :pending
165
+ # after_transition pending: :parked, do: :track_initial_transition
166
+ #
148
167
  # event :park do
149
- # transition :pending => :parked
168
+ # transition pending: :parked
150
169
  # end
151
- #
152
- # state :pending, :value => nil
170
+ #
171
+ # state :pending, value: nil
153
172
  # end
154
173
  # end
155
- #
174
+ #
156
175
  # vehicle = Vehicle.new
157
176
  # vehicle.park
158
- #
177
+ #
159
178
  # * Use a default event attribute that will automatically trigger when the
160
179
  # configured action gets run (note that the <tt>:pending</tt> state is
161
180
  # actually stored as nil):
162
- #
181
+ #
163
182
  # class Vehicle < ActiveRecord::Base
164
- # state_machine :initial => :pending
165
- # after_transition :pending => :parked, :do => :track_initial_transition
166
- #
183
+ # state_machine initial: :pending
184
+ # after_transition pending: :parked, do: :track_initial_transition
185
+ #
167
186
  # event :park do
168
- # transition :pending => :parked
187
+ # transition pending: :parked
169
188
  # end
170
- #
171
- # state :pending, :value => nil
189
+ #
190
+ # state :pending, value: nil
172
191
  # end
173
- #
192
+ #
174
193
  # def initialize(*)
175
194
  # super
176
195
  # self.state_event = 'park'
177
196
  # end
178
197
  # end
179
- #
198
+ #
180
199
  # vehicle = Vehicle.new
181
200
  # vehicle.save
182
- #
201
+ #
183
202
  # === Canceling callbacks
184
- #
203
+ #
185
204
  # Callbacks can be canceled by throwing :halt at any point during the
186
205
  # callback. For example,
187
- #
206
+ #
188
207
  # ...
189
208
  # throw :halt
190
209
  # ...
191
- #
210
+ #
192
211
  # If a +before+ callback halts the chain, the associated transition and all
193
212
  # later callbacks are canceled. If an +after+ callback halts the chain,
194
213
  # the later callbacks are canceled, but the transition is still successful.
195
- #
214
+ #
196
215
  # These same rules apply to +around+ callbacks with the exception that any
197
216
  # +around+ callback that doesn't yield will essentially result in :halt being
198
217
  # thrown. Any code executed after the yield will behave in the same way as
199
218
  # +after+ callbacks.
200
- #
219
+ #
201
220
  # *Note* that if a +before+ callback fails and the bang version of an event
202
221
  # was invoked, an exception will be raised instead of returning false. For
203
222
  # example,
204
- #
223
+ #
205
224
  # class Vehicle
206
225
  # state_machine :initial => :parked do
207
226
  # before_transition any => :idling, :do => lambda {|vehicle| throw :halt}
208
227
  # ...
209
228
  # end
210
229
  # end
211
- #
230
+ #
212
231
  # vehicle = Vehicle.new
213
232
  # vehicle.park # => false
214
233
  # vehicle.park! # => StateMachines::InvalidTransition: Cannot transition state via :park from "idling"
215
- #
234
+ #
216
235
  # == Observers
217
- #
236
+ #
218
237
  # Observers, in the sense of external classes and *not* Ruby's Observable
219
238
  # mechanism, can hook into state machines as well. Such observers use the
220
239
  # same callback api that's used internally.
221
- #
240
+ #
222
241
  # Below are examples of defining observers for the following state machine:
223
- #
242
+ #
224
243
  # class Vehicle
225
244
  # state_machine do
226
245
  # event :park do
227
- # transition :idling => :parked
246
+ # transition idling: :parked
228
247
  # end
229
248
  # ...
230
249
  # end
231
250
  # ...
232
251
  # end
233
- #
252
+ #
234
253
  # Event/Transition behaviors:
235
- #
254
+ #
236
255
  # class VehicleObserver
237
256
  # def self.before_park(vehicle, transition)
238
257
  # logger.info "#{vehicle} instructed to park... state is: #{transition.from}, state will be: #{transition.to}"
239
258
  # end
240
- #
259
+ #
241
260
  # def self.after_park(vehicle, transition, result)
242
261
  # logger.info "#{vehicle} instructed to park... state was: #{transition.from}, state is: #{transition.to}"
243
262
  # end
244
- #
263
+ #
245
264
  # def self.before_transition(vehicle, transition)
246
265
  # logger.info "#{vehicle} instructed to #{transition.event}... #{transition.attribute} is: #{transition.from}, #{transition.attribute} will be: #{transition.to}"
247
266
  # end
248
- #
267
+ #
249
268
  # def self.after_transition(vehicle, transition)
250
269
  # logger.info "#{vehicle} instructed to #{transition.event}... #{transition.attribute} was: #{transition.from}, #{transition.attribute} is: #{transition.to}"
251
270
  # end
252
- #
271
+ #
253
272
  # def self.around_transition(vehicle, transition)
254
273
  # logger.info Benchmark.measure { yield }
255
274
  # end
256
275
  # end
257
- #
276
+ #
258
277
  # Vehicle.state_machine do
259
278
  # before_transition :on => :park, :do => VehicleObserver.method(:before_park)
260
279
  # before_transition VehicleObserver.method(:before_transition)
261
- #
280
+ #
262
281
  # after_transition :on => :park, :do => VehicleObserver.method(:after_park)
263
282
  # after_transition VehicleObserver.method(:after_transition)
264
- #
283
+ #
265
284
  # around_transition VehicleObserver.method(:around_transition)
266
285
  # end
267
- #
286
+ #
268
287
  # One common callback is to record transitions for all models in the system
269
288
  # for auditing/debugging purposes. Below is an example of an observer that
270
289
  # can easily automate this process for all models:
271
- #
290
+ #
272
291
  # class StateMachineObserver
273
292
  # def self.before_transition(object, transition)
274
293
  # Audit.log_transition(object.attributes)
275
294
  # end
276
295
  # end
277
- #
296
+ #
278
297
  # [Vehicle, Switch, Project].each do |klass|
279
298
  # klass.state_machines.each do |attribute, machine|
280
299
  # machine.before_transition StateMachineObserver.method(:before_transition)
281
300
  # end
282
301
  # end
283
- #
302
+ #
284
303
  # Additional observer-like behavior may be exposed by the various integrations
285
304
  # available. See below for more information on integrations.
286
- #
305
+ #
287
306
  # == Overriding instance / class methods
288
- #
307
+ #
289
308
  # Hooking in behavior to the generated instance / class methods from the
290
309
  # state machine, events, and states is very simple because of the way these
291
310
  # methods are generated on the class. Using the class's ancestors, the
292
311
  # original generated method can be referred to via +super+. For example,
293
- #
312
+ #
294
313
  # class Vehicle
295
314
  # state_machine do
296
315
  # event :park do
297
316
  # ...
298
317
  # end
299
318
  # end
300
- #
319
+ #
301
320
  # def park(*args)
302
321
  # logger.info "..."
303
322
  # super
304
323
  # end
305
324
  # end
306
- #
325
+ #
307
326
  # In the above example, the +park+ instance method that's generated on the
308
327
  # Vehicle class (by the associated event) is overridden with custom behavior.
309
328
  # Once this behavior is complete, the original method from the state machine
310
329
  # is invoked by simply calling +super+.
311
- #
330
+ #
312
331
  # The same technique can be used for +state+, +state_name+, and all other
313
332
  # instance *and* class methods on the Vehicle class.
314
333
  #
315
334
  # == Method conflicts
316
- #
335
+ #
317
336
  # By default state_machine does not redefine methods that exist on
318
337
  # superclasses (*including* Object) or any modules (*including* Kernel) that
319
338
  # were included before it was defined. This is in order to ensure that
320
339
  # existing behavior on the class is not broken by the inclusion of
321
340
  # state_machine.
322
- #
341
+ #
323
342
  # If a conflicting method is detected, state_machine will generate a warning.
324
343
  # For example, consider the following class:
325
- #
344
+ #
326
345
  # class Vehicle
327
346
  # state_machine do
328
347
  # event :open do
@@ -330,67 +349,67 @@ module StateMachines
330
349
  # end
331
350
  # end
332
351
  # end
333
- #
352
+ #
334
353
  # In the above class, an event named "open" is defined for its state machine.
335
354
  # However, "open" is already defined as an instance method in Ruby's Kernel
336
355
  # module that gets included in every Object. As a result, state_machine will
337
356
  # generate the following warning:
338
- #
357
+ #
339
358
  # Instance method "open" is already defined in Object, use generic helper instead or set StateMachines::Machine.ignore_method_conflicts = true.
340
- #
359
+ #
341
360
  # Even though you may not be using Kernel's implementation of the "open"
342
361
  # instance method, state_machine isn't aware of this and, as a result, stays
343
362
  # safe and just skips redefining the method.
344
- #
363
+ #
345
364
  # As with almost all helpers methods defined by state_machine in your class,
346
365
  # there are generic methods available for working around this method conflict.
347
366
  # In the example above, you can invoke the "open" event like so:
348
- #
367
+ #
349
368
  # vehicle = Vehicle.new # => #<Vehicle:0xb72686b4 @state=nil>
350
369
  # vehicle.fire_events(:open) # => true
351
- #
370
+ #
352
371
  # # This will not work
353
372
  # vehicle.open # => NoMethodError: private method `open' called for #<Vehicle:0xb72686b4 @state=nil>
354
- #
373
+ #
355
374
  # If you want to take on the risk of overriding existing methods and just
356
375
  # ignore method conflicts altogether, you can do so by setting the following
357
376
  # configuration:
358
- #
377
+ #
359
378
  # StateMachines::Machine.ignore_method_conflicts = true
360
- #
379
+ #
361
380
  # This will allow you to define events like "open" as described above and
362
381
  # still generate the "open" instance helper method. For example:
363
- #
382
+ #
364
383
  # StateMachines::Machine.ignore_method_conflicts = true
365
- #
384
+ #
366
385
  # class Vehicle
367
386
  # state_machine do
368
387
  # event :open do
369
388
  # ...
370
389
  # end
371
390
  # end
372
- #
391
+ #
373
392
  # vehicle = Vehicle.new # => #<Vehicle:0xb72686b4 @state=nil>
374
393
  # vehicle.open # => true
375
- #
394
+ #
376
395
  # By default, state_machine helps prevent you from making mistakes and
377
396
  # accidentally overriding methods that you didn't intend to. Once you
378
397
  # understand this and what the consequences are, setting the
379
398
  # +ignore_method_conflicts+ option is a perfectly reasonable workaround.
380
- #
399
+ #
381
400
  # == Integrations
382
- #
401
+ #
383
402
  # By default, state machines are library-agnostic, meaning that they work
384
403
  # on any Ruby class and have no external dependencies. However, there are
385
404
  # certain libraries which expose additional behavior that can be taken
386
405
  # advantage of by state machines.
387
- #
406
+ #
388
407
  # This library is built to work out of the box with a few popular Ruby
389
408
  # libraries that allow for additional behavior to provide a cleaner and
390
409
  # smoother experience. This is especially the case for objects backed by a
391
410
  # database that may allow for transactions, persistent storage,
392
411
  # search/filters, callbacks, etc.
393
- #
412
+ #
394
413
  # When a state machine is defined for classes using any of the above libraries,
395
414
  # it will try to automatically determine the integration to use (Agnostic,
396
415
  # ActiveModel, ActiveRecord, DataMapper, Mongoid, MongoMapper, or Sequel)
@@ -398,67 +417,25 @@ module StateMachines
398
417
  # machine's behavior, refer to all constants defined under the
399
418
  # StateMachines::Integrations namespace.
400
419
  class Machine
401
-
420
+ extend ClassMethods
402
421
  include EvalHelpers
403
422
  include MatcherHelpers
404
-
405
- class << self
406
- # Attempts to find or create a state machine for the given class. For
407
- # example,
408
- #
409
- # StateMachines::Machine.find_or_create(Vehicle)
410
- # StateMachines::Machine.find_or_create(Vehicle, :initial => :parked)
411
- # StateMachines::Machine.find_or_create(Vehicle, :status)
412
- # StateMachines::Machine.find_or_create(Vehicle, :status, :initial => :parked)
413
- #
414
- # If a machine of the given name already exists in one of the class's
415
- # superclasses, then a copy of that machine will be created and stored
416
- # in the new owner class (the original will remain unchanged).
417
- def find_or_create(owner_class, *args, &block)
418
- options = args.last.is_a?(Hash) ? args.pop : {}
419
- name = args.first || :state
420
-
421
- # Find an existing machine
422
- machine = owner_class.respond_to?(:state_machines) &&
423
- (args.first && owner_class.state_machines[name] || !args.first &&
424
- owner_class.state_machines.values.first) || nil
425
-
426
- if machine
427
- # Only create a new copy if changes are being made to the machine in
428
- # a subclass
429
- if machine.owner_class != owner_class && (options.any? || block_given?)
430
- machine = machine.clone
431
- machine.initial_state = options[:initial] if options.include?(:initial)
432
- machine.owner_class = owner_class
433
- end
434
-
435
- # Evaluate DSL
436
- machine.instance_eval(&block) if block_given?
437
- else
438
- # No existing machine: create a new one
439
- machine = new(owner_class, name, options, &block)
440
- end
441
-
442
- machine
443
- end
444
-
445
-
446
- def draw(*)
447
- fail NotImplementedError
448
- end
449
-
450
- # Default messages to use for validation errors in ORM integrations
451
- attr_accessor :default_messages
452
- attr_accessor :ignore_method_conflicts
453
- end
454
- @default_messages = {
455
- :invalid => 'is invalid',
456
- :invalid_event => 'cannot transition when %s',
457
- :invalid_transition => 'cannot transition via "%1$s"'
458
- }
423
+ include Utilities
424
+ include Parsing
425
+ include Validation
426
+ include HelperGenerators
427
+ include ActionHooks
428
+ include Scoping
429
+ include Configuration
430
+ include StateMethods
431
+ include EventMethods
432
+ include Callbacks
433
+ include Rendering
434
+ include Integration
459
435
 
460
436
  # Whether to ignore any conflicts that are detected for helper methods that
461
437
  # get generated for a machine's owner class. Default is false.
438
+ # Thread-safe via atomic reference updates
462
439
  @ignore_method_conflicts = false
463
440
 
464
441
  # The class that the machine is defined in
@@ -479,12 +456,12 @@ module StateMachines
479
456
  # * Event transitions (:to, :from, and :except_from options)
480
457
  # * Transition callbacks (:to, :from, :except_to, and :except_from options)
481
458
  # * Unreferenced states (using +other_states+ helper)
482
- #
459
+ #
483
460
  # These are sorted, by default, in the order in which they were referenced.
484
461
  attr_reader :states
485
462
 
486
463
  # The callbacks to invoke before/after a transition is performed
487
- #
464
+ #
488
465
  # Maps :before => callbacks and :after => callbacks
489
466
  attr_reader :callbacks
490
467
 
@@ -500,243 +477,100 @@ module StateMachines
500
477
  attr_reader :use_transactions
501
478
 
502
479
  # Creates a new state machine for the given attribute
503
- def initialize(owner_class, *args, &block)
504
- options = args.last.is_a?(Hash) ? args.pop : {}
505
- options.assert_valid_keys(:attribute, :initial, :initialize, :action, :plural, :namespace, :integration, :messages, :use_transactions)
506
-
507
- # Find an integration that matches this machine's owner class
508
- if options.include?(:integration)
509
- @integration = options[:integration] && StateMachines::Integrations.find_by_name(options[:integration])
510
- else
511
- @integration = StateMachines::Integrations.match(owner_class)
512
- end
513
-
514
- if @integration
515
- extend @integration
516
- options = (@integration.defaults || {}).merge(options)
517
- end
518
-
519
- # Add machine-wide defaults
520
- options = {:use_transactions => true, :initialize => true}.merge(options)
521
-
522
- # Set machine configuration
523
- @name = args.first || :state
524
- @attribute = options[:attribute] || @name
525
- @events = EventCollection.new(self)
526
- @states = StateCollection.new(self)
527
- @callbacks = {:before => [], :after => [], :failure => []}
528
- @namespace = options[:namespace]
529
- @messages = options[:messages] || {}
530
- @action = options[:action]
531
- @use_transactions = options[:use_transactions]
532
- @initialize_state = options[:initialize]
533
- @action_hook_defined = false
534
- self.owner_class = owner_class
535
-
536
- # Merge with sibling machine configurations
537
- add_sibling_machine_configs
538
-
539
- # Define class integration
540
- define_helpers
541
- define_scopes(options[:plural])
542
- after_initialize
543
-
544
- # Evaluate DSL
545
- instance_eval(&block) if block_given?
546
- self.initial_state = options[:initial] unless sibling_machines.any?
547
- end
548
-
549
- # Creates a copy of this machine in addition to copies of each associated
550
- # event/states/callback, so that the modifications to those collections do
551
- # not affect the original machine.
552
- def initialize_copy(orig) #:nodoc:
553
- super
554
-
555
- @events = @events.dup
556
- @events.machine = self
557
- @states = @states.dup
558
- @states.machine = self
559
- @callbacks = {:before => @callbacks[:before].dup, :after => @callbacks[:after].dup, :failure => @callbacks[:failure].dup}
560
- end
561
-
562
- # Sets the class which is the owner of this state machine. Any methods
563
- # generated by states, events, or other parts of the machine will be defined
564
- # on the given owner class.
565
- def owner_class=(klass)
566
- @owner_class = klass
567
-
568
- # Create modules for extending the class with state/event-specific methods
569
- @helper_modules = helper_modules = {:instance => HelperModule.new(self, :instance), :class => HelperModule.new(self, :class)}
570
- owner_class.class_eval do
571
- extend helper_modules[:class]
572
- include helper_modules[:instance]
573
- end
574
-
575
- # Add class-/instance-level methods to the owner class for state initialization
576
- unless owner_class < StateMachines::InstanceMethods
577
- owner_class.class_eval do
578
- extend StateMachines::ClassMethods
579
- include StateMachines::InstanceMethods
580
- end
581
-
582
- define_state_initializer if @initialize_state
583
- end
584
-
585
- # Record this machine as matched to the name in the current owner class.
586
- # This will override any machines mapped to the same name in any superclasses.
587
- owner_class.state_machines[name] = self
588
- end
589
-
590
- # Sets the initial state of the machine. This can be either the static name
591
- # of a state or a lambda block which determines the initial state at
592
- # creation time.
593
- def initial_state=(new_initial_state)
594
- @initial_state = new_initial_state
595
- add_states([@initial_state]) unless dynamic_initial_state?
596
-
597
- # Update all states to reflect the new initial state
598
- states.each { |state| state.initial = (state.name == @initial_state) }
599
-
600
- # Output a warning if there are conflicting initial states for the machine's
601
- # attribute
602
- initial_state = states.detect { |state| state.initial }
603
- if !owner_class_attribute_default.nil? && (dynamic_initial_state? || !owner_class_attribute_default_matches?(initial_state))
604
- warn(
605
- "Both #{owner_class.name} and its #{name.inspect} machine have defined "\
606
- "a different default for \"#{attribute}\". Use only one or the other for "\
607
- "defining defaults to avoid unexpected behaviors."
608
- )
609
- end
610
- end
611
480
 
612
481
  # Gets the initial state of the machine for the given object. If a dynamic
613
482
  # initial state was configured for this machine, then the object will be
614
483
  # passed into the lambda block to help determine the actual state.
615
- #
484
+ #
616
485
  # == Examples
617
- #
486
+ #
618
487
  # With a static initial state:
619
- #
488
+ #
620
489
  # class Vehicle
621
490
  # state_machine :initial => :parked do
622
491
  # ...
623
492
  # end
624
493
  # end
625
- #
494
+ #
626
495
  # vehicle = Vehicle.new
627
496
  # Vehicle.state_machine.initial_state(vehicle) # => #<StateMachines::State name=:parked value="parked" initial=true>
628
- #
497
+ #
629
498
  # With a dynamic initial state:
630
- #
499
+ #
631
500
  # class Vehicle
632
501
  # attr_accessor :force_idle
633
- #
502
+ #
634
503
  # state_machine :initial => lambda {|vehicle| vehicle.force_idle ? :idling : :parked} do
635
504
  # ...
636
505
  # end
637
506
  # end
638
- #
507
+ #
639
508
  # vehicle = Vehicle.new
640
- #
509
+ #
641
510
  # vehicle.force_idle = true
642
511
  # Vehicle.state_machine.initial_state(vehicle) # => #<StateMachines::State name=:idling value="idling" initial=false>
643
- #
512
+ #
644
513
  # vehicle.force_idle = false
645
514
  # Vehicle.state_machine.initial_state(vehicle) # => #<StateMachines::State name=:parked value="parked" initial=false>
646
- def initial_state(object)
647
- states.fetch(dynamic_initial_state? ? evaluate_method(object, @initial_state) : @initial_state) if instance_variable_defined?('@initial_state')
648
- end
649
-
650
- # Whether a dynamic initial state is being used in the machine
651
- def dynamic_initial_state?
652
- instance_variable_defined?('@initial_state') && @initial_state.is_a?(Proc)
653
- end
654
-
655
- # Initializes the state on the given object. Initial values are only set if
656
- # the machine's attribute hasn't been previously initialized.
657
- #
658
- # Configuration options:
659
- # * <tt>:force</tt> - Whether to initialize the state regardless of its
660
- # current value
661
- # * <tt>:to</tt> - A hash to set the initial value in instead of writing
662
- # directly to the object
663
- def initialize_state(object, options = {})
664
- state = initial_state(object)
665
- if state && (options[:force] || initialize_state?(object))
666
- value = state.value
667
-
668
- if hash = options[:to]
669
- hash[attribute.to_s] = value
670
- else
671
- write(object, :state, value)
672
- end
673
- end
674
- end
675
-
676
- # Gets the actual name of the attribute on the machine's owner class that
677
- # stores data with the given name.
678
- def attribute(name = :state)
679
- name == :state ? @attribute : :"#{self.name}_#{name}"
680
- end
681
515
 
682
516
  # Defines a new helper method in an instance or class scope with the given
683
517
  # name. If the method is already defined in the scope, then this will not
684
518
  # override it.
685
- #
519
+ #
686
520
  # If passing in a block, there are two side effects to be aware of
687
521
  # 1. The method cannot be chained, meaning that the block cannot call +super+
688
522
  # 2. If the method is already defined in an ancestor, then it will not get
689
523
  # overridden and a warning will be output.
690
- #
524
+ #
691
525
  # Example:
692
- #
526
+ #
693
527
  # # Instance helper
694
528
  # machine.define_helper(:instance, :state_name) do |machine, object|
695
529
  # machine.states.match(object).name
696
530
  # end
697
- #
531
+ #
698
532
  # # Class helper
699
533
  # machine.define_helper(:class, :state_machine_name) do |machine, klass|
700
534
  # "State"
701
535
  # end
702
- #
536
+ #
703
537
  # You can also define helpers using string evaluation like so:
704
- #
538
+ #
705
539
  # # Instance helper
706
540
  # machine.define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
707
541
  # def state_name
708
542
  # self.class.state_machine(:state).states.match(self).name
709
543
  # end
710
544
  # end_eval
711
- #
545
+ #
712
546
  # # Class helper
713
547
  # machine.define_helper :class, <<-end_eval, __FILE__, __LINE__ + 1
714
548
  # def state_machine_name
715
549
  # "State"
716
550
  # end
717
551
  # end_eval
718
- def define_helper(scope, method, *args, &block)
552
+ def define_helper(scope, method, *, **, &block)
719
553
  helper_module = @helper_modules.fetch(scope)
720
554
 
721
555
  if block_given?
722
- if !self.class.ignore_method_conflicts && conflicting_ancestor = owner_class_ancestor_has_method?(scope, method)
556
+ if !self.class.ignore_method_conflicts && (conflicting_ancestor = owner_class_ancestor_has_method?(scope, method))
723
557
  ancestor_name = conflicting_ancestor.name && !conflicting_ancestor.name.empty? ? conflicting_ancestor.name : conflicting_ancestor.to_s
724
558
  warn "#{scope == :class ? 'Class' : 'Instance'} method \"#{method}\" is already defined in #{ancestor_name}, use generic helper instead or set StateMachines::Machine.ignore_method_conflicts = true."
725
559
  else
726
560
  name = self.name
727
561
  helper_module.class_eval do
728
- define_method(method) do |*block_args|
729
- block.call((scope == :instance ? self.class : self).state_machine(name), self, *block_args)
562
+ define_method(method) do |*args, **kwargs|
563
+ block.call((scope == :instance ? self.class : self).state_machine(name), self, *args, **kwargs)
730
564
  end
731
565
  end
732
566
  end
733
567
  else
734
- helper_module.class_eval(method, *args)
568
+ helper_module.class_eval(method, __FILE__, __LINE__)
735
569
  end
736
570
  end
737
571
 
738
572
  # Customizes the definition of one or more states in the machine.
739
- #
573
+ #
740
574
  # Configuration options:
741
575
  # * <tt>:value</tt> - The actual value to store when an object transitions
742
576
  # to the state. Default is the name (stringified).
@@ -748,13 +582,13 @@ module StateMachines
748
582
  # * <tt>:human_name</tt> - The human-readable version of this state's name.
749
583
  # By default, this is either defined by the integration or stringifies the
750
584
  # name and converts underscores to spaces.
751
- #
585
+ #
752
586
  # == Customizing the stored value
753
- #
587
+ #
754
588
  # Whenever a state is automatically discovered in the state machine, its
755
589
  # default value is assumed to be the stringified version of the name. For
756
590
  # example,
757
- #
591
+ #
758
592
  # class Vehicle
759
593
  # state_machine :initial => :parked do
760
594
  # event :ignite do
@@ -762,124 +596,124 @@ module StateMachines
762
596
  # end
763
597
  # end
764
598
  # end
765
- #
599
+ #
766
600
  # In the above state machine, there are two states automatically discovered:
767
601
  # :parked and :idling. These states, by default, will store their stringified
768
602
  # equivalents when an object moves into that state (e.g. "parked" / "idling").
769
- #
603
+ #
770
604
  # For legacy systems or when tying state machines into existing frameworks,
771
605
  # it's oftentimes necessary to need to store a different value for a state
772
606
  # than the default. In order to continue taking advantage of an expressive
773
607
  # state machine and helper methods, every defined state can be re-configured
774
608
  # with a custom stored value. For example,
775
- #
609
+ #
776
610
  # class Vehicle
777
611
  # state_machine :initial => :parked do
778
612
  # event :ignite do
779
613
  # transition :parked => :idling
780
614
  # end
781
- #
615
+ #
782
616
  # state :idling, :value => 'IDLING'
783
617
  # state :parked, :value => 'PARKED
784
618
  # end
785
619
  # end
786
- #
620
+ #
787
621
  # This is also useful if being used in association with a database and,
788
622
  # instead of storing the state name in a column, you want to store the
789
623
  # state's foreign key:
790
- #
624
+ #
791
625
  # class VehicleState < ActiveRecord::Base
792
626
  # end
793
- #
627
+ #
794
628
  # class Vehicle < ActiveRecord::Base
795
629
  # state_machine :attribute => :state_id, :initial => :parked do
796
630
  # event :ignite do
797
631
  # transition :parked => :idling
798
632
  # end
799
- #
633
+ #
800
634
  # states.each do |state|
801
635
  # self.state(state.name, :value => lambda { VehicleState.find_by_name(state.name.to_s).id }, :cache => true)
802
636
  # end
803
637
  # end
804
638
  # end
805
- #
639
+ #
806
640
  # In the above example, each known state is configured to store it's
807
641
  # associated database id in the +state_id+ attribute. Also, notice that a
808
642
  # lambda block is used to define the state's value. This is required in
809
643
  # situations (like testing) where the model is loaded without any existing
810
644
  # data (i.e. no VehicleState records available).
811
- #
645
+ #
812
646
  # One caveat to the above example is to keep performance in mind. To avoid
813
647
  # constant db hits for looking up the VehicleState ids, the value is cached
814
648
  # by specifying the <tt>:cache</tt> option. Alternatively, a custom
815
649
  # caching strategy can be used like so:
816
- #
650
+ #
817
651
  # class VehicleState < ActiveRecord::Base
818
652
  # cattr_accessor :cache_store
819
653
  # self.cache_store = ActiveSupport::Cache::MemoryStore.new
820
- #
654
+ #
821
655
  # def self.find_by_name(name)
822
656
  # cache_store.fetch(name) { find(:first, :conditions => {:name => name}) }
823
657
  # end
824
658
  # end
825
- #
659
+ #
826
660
  # === Dynamic values
827
- #
661
+ #
828
662
  # In addition to customizing states with other value types, lambda blocks
829
663
  # can also be specified to allow for a state's value to be determined
830
664
  # dynamically at runtime. For example,
831
- #
665
+ #
832
666
  # class Vehicle
833
667
  # state_machine :purchased_at, :initial => :available do
834
668
  # event :purchase do
835
669
  # transition all => :purchased
836
670
  # end
837
- #
671
+ #
838
672
  # event :restock do
839
673
  # transition all => :available
840
674
  # end
841
- #
675
+ #
842
676
  # state :available, :value => nil
843
677
  # state :purchased, :if => lambda {|value| !value.nil?}, :value => lambda {Time.now}
844
678
  # end
845
679
  # end
846
- #
680
+ #
847
681
  # In the above definition, the <tt>:purchased</tt> state is customized with
848
682
  # both a dynamic value *and* a value matcher.
849
- #
683
+ #
850
684
  # When an object transitions to the purchased state, the value's lambda
851
685
  # block will be called. This will get the current time and store it in the
852
686
  # object's +purchased_at+ attribute.
853
- #
687
+ #
854
688
  # *Note* that the custom matcher is very important here. Since there's no
855
689
  # way for the state machine to figure out an object's state when it's set to
856
690
  # a runtime value, it must be explicitly defined. If the <tt>:if</tt> option
857
691
  # were not configured for the state, then an ArgumentError exception would
858
692
  # be raised at runtime, indicating that the state machine could not figure
859
693
  # out what the current state of the object was.
860
- #
694
+ #
861
695
  # == Behaviors
862
- #
696
+ #
863
697
  # Behaviors define a series of methods to mixin with objects when the current
864
698
  # state matches the given one(s). This allows instance methods to behave
865
699
  # a specific way depending on what the value of the object's state is.
866
- #
700
+ #
867
701
  # For example,
868
- #
702
+ #
869
703
  # class Vehicle
870
704
  # attr_accessor :driver
871
705
  # attr_accessor :passenger
872
- #
706
+ #
873
707
  # state_machine :initial => :parked do
874
708
  # event :ignite do
875
709
  # transition :parked => :idling
876
710
  # end
877
- #
711
+ #
878
712
  # state :parked do
879
713
  # def speed
880
714
  # 0
881
715
  # end
882
- #
716
+ #
883
717
  # def rotate_driver
884
718
  # driver = self.driver
885
719
  # self.driver = passenger
@@ -887,97 +721,97 @@ module StateMachines
887
721
  # true
888
722
  # end
889
723
  # end
890
- #
724
+ #
891
725
  # state :idling, :first_gear do
892
726
  # def speed
893
727
  # 20
894
728
  # end
895
- #
729
+ #
896
730
  # def rotate_driver
897
731
  # self.state = 'parked'
898
732
  # rotate_driver
899
733
  # end
900
734
  # end
901
- #
735
+ #
902
736
  # other_states :backing_up
903
737
  # end
904
738
  # end
905
- #
739
+ #
906
740
  # In the above example, there are two dynamic behaviors defined for the
907
741
  # class:
908
742
  # * +speed+
909
743
  # * +rotate_driver+
910
- #
744
+ #
911
745
  # Each of these behaviors are instance methods on the Vehicle class. However,
912
746
  # which method actually gets invoked is based on the current state of the
913
747
  # object. Using the above class as the example:
914
- #
748
+ #
915
749
  # vehicle = Vehicle.new
916
750
  # vehicle.driver = 'John'
917
751
  # vehicle.passenger = 'Jane'
918
- #
752
+ #
919
753
  # # Behaviors in the "parked" state
920
754
  # vehicle.state # => "parked"
921
755
  # vehicle.speed # => 0
922
756
  # vehicle.rotate_driver # => true
923
757
  # vehicle.driver # => "Jane"
924
758
  # vehicle.passenger # => "John"
925
- #
759
+ #
926
760
  # vehicle.ignite # => true
927
- #
761
+ #
928
762
  # # Behaviors in the "idling" state
929
763
  # vehicle.state # => "idling"
930
764
  # vehicle.speed # => 20
931
765
  # vehicle.rotate_driver # => true
932
766
  # vehicle.driver # => "John"
933
767
  # vehicle.passenger # => "Jane"
934
- #
768
+ #
935
769
  # As can be seen, both the +speed+ and +rotate_driver+ instance method
936
770
  # implementations changed how they behave based on what the current state
937
771
  # of the vehicle was.
938
- #
772
+ #
939
773
  # === Invalid behaviors
940
- #
774
+ #
941
775
  # If a specific behavior has not been defined for a state, then a
942
776
  # NoMethodError exception will be raised, indicating that that method would
943
777
  # not normally exist for an object with that state.
944
- #
778
+ #
945
779
  # Using the example from before:
946
- #
780
+ #
947
781
  # vehicle = Vehicle.new
948
782
  # vehicle.state = 'backing_up'
949
783
  # vehicle.speed # => NoMethodError: undefined method 'speed' for #<Vehicle:0xb7d296ac> in state "backing_up"
950
- #
784
+ #
951
785
  # === Using matchers
952
- #
786
+ #
953
787
  # The +all+ / +any+ matchers can be used to easily define behaviors for a
954
788
  # group of states. Note, however, that you cannot use these matchers to
955
789
  # set configurations for states. Behaviors using these matchers can be
956
790
  # defined at any point in the state machine and will always get applied to
957
791
  # the proper states.
958
- #
792
+ #
959
793
  # For example:
960
- #
794
+ #
961
795
  # state_machine :initial => :parked do
962
796
  # ...
963
- #
797
+ #
964
798
  # state all - [:parked, :idling, :stalled] do
965
799
  # validates_presence_of :speed
966
- #
800
+ #
967
801
  # def speed
968
802
  # gear * 10
969
803
  # end
970
804
  # end
971
805
  # end
972
- #
806
+ #
973
807
  # == State-aware class methods
974
- #
808
+ #
975
809
  # In addition to defining scopes for instance methods that are state-aware,
976
810
  # the same can be done for certain types of class methods.
977
- #
811
+ #
978
812
  # Some libraries have support for class-level methods that only run certain
979
813
  # behaviors based on a conditions hash passed in. For example:
980
- #
814
+ #
981
815
  # class Vehicle < ActiveRecord::Base
982
816
  # state_machine do
983
817
  # ...
@@ -987,110 +821,35 @@ module StateMachines
987
821
  # end
988
822
  # end
989
823
  # end
990
- #
824
+ #
991
825
  # In the above ActiveRecord model, two validations have been defined which
992
826
  # will *only* run when the Vehicle object is in one of the three states:
993
827
  # +first_gear+, +second_gear+, or +third_gear. Notice, also, that if/unless
994
828
  # conditions can continue to be used.
995
- #
829
+ #
996
830
  # This functionality is not library-specific and can work for any class-level
997
831
  # method that is defined like so:
998
- #
832
+ #
999
833
  # def validates_presence_of(attribute, options = {})
1000
834
  # ...
1001
835
  # end
1002
- #
836
+ #
1003
837
  # The minimum requirement is that the last argument in the method be an
1004
838
  # options hash which contains at least <tt>:if</tt> condition support.
1005
- def state(*names, &block)
1006
- options = names.last.is_a?(Hash) ? names.pop : {}
1007
- options.assert_valid_keys(:value, :cache, :if, :human_name)
1008
-
1009
- # Store the context so that it can be used for / matched against any state
1010
- # that gets added
1011
- @states.context(names, &block) if block_given?
1012
-
1013
- if names.first.is_a?(Matcher)
1014
- # Add any states referenced in the matcher. When matchers are used,
1015
- # states are not allowed to be configured.
1016
- raise ArgumentError, "Cannot configure states when using matchers (using #{options.inspect})" if options.any?
1017
- states = add_states(names.first.values)
1018
- else
1019
- states = add_states(names)
1020
-
1021
- # Update the configuration for the state(s)
1022
- states.each do |state|
1023
- if options.include?(:value)
1024
- state.value = options[:value]
1025
- self.states.update(state)
1026
- end
1027
-
1028
- state.human_name = options[:human_name] if options.include?(:human_name)
1029
- state.cache = options[:cache] if options.include?(:cache)
1030
- state.matcher = options[:if] if options.include?(:if)
1031
- end
1032
- end
1033
-
1034
- states.length == 1 ? states.first : states
1035
- end
1036
-
1037
- alias_method :other_states, :state
1038
-
1039
- # Gets the current value stored in the given object's attribute.
1040
- #
1041
- # For example,
1042
- #
1043
- # class Vehicle
1044
- # state_machine :initial => :parked do
1045
- # ...
1046
- # end
1047
- # end
1048
- #
1049
- # vehicle = Vehicle.new # => #<Vehicle:0xb7d94ab0 @state="parked">
1050
- # Vehicle.state_machine.read(vehicle, :state) # => "parked" # Equivalent to vehicle.state
1051
- # Vehicle.state_machine.read(vehicle, :event) # => nil # Equivalent to vehicle.state_event
1052
- def read(object, attribute, ivar = false)
1053
- attribute = self.attribute(attribute)
1054
- if ivar
1055
- object.instance_variable_defined?("@#{attribute}") ? object.instance_variable_get("@#{attribute}") : nil
1056
- else
1057
- object.send(attribute)
1058
- end
1059
- end
1060
-
1061
- # Sets a new value in the given object's attribute.
1062
- #
1063
- # For example,
1064
- #
1065
- # class Vehicle
1066
- # state_machine :initial => :parked do
1067
- # ...
1068
- # end
1069
- # end
1070
- #
1071
- # vehicle = Vehicle.new # => #<Vehicle:0xb7d94ab0 @state="parked">
1072
- # Vehicle.state_machine.write(vehicle, :state, 'idling') # => Equivalent to vehicle.state = 'idling'
1073
- # Vehicle.state_machine.write(vehicle, :event, 'park') # => Equivalent to vehicle.state_event = 'park'
1074
- # vehicle.state # => "idling"
1075
- # vehicle.event # => "park"
1076
- def write(object, attribute, value, ivar = false)
1077
- attribute = self.attribute(attribute)
1078
- ivar ? object.instance_variable_set("@#{attribute}", value) : object.send("#{attribute}=", value)
1079
- end
1080
839
 
1081
840
  # Defines one or more events for the machine and the transitions that can
1082
841
  # be performed when those events are run.
1083
- #
842
+ #
1084
843
  # This method is also aliased as +on+ for improved compatibility with
1085
844
  # using a domain-specific language.
1086
- #
845
+ #
1087
846
  # Configuration options:
1088
847
  # * <tt>:human_name</tt> - The human-readable version of this event's name.
1089
848
  # By default, this is either defined by the integration or stringifies the
1090
849
  # name and converts underscores to spaces.
1091
- #
850
+ #
1092
851
  # == Instance methods
1093
- #
852
+ #
1094
853
  # The following instance methods are generated when a new event is defined
1095
854
  # (the "park" event is used as an example):
1096
855
  # * <tt>park(..., run_action = true)</tt> - Fires the "park" event,
@@ -1114,13 +873,13 @@ module StateMachines
1114
873
  # object or nil if no transitions can be performed. Like <tt>can_park?</tt>
1115
874
  # this will also *not* run validations or callbacks. It will only
1116
875
  # determine if the state machine defines a valid transition for the event.
1117
- #
876
+ #
1118
877
  # With a namespace of "car", the above names map to the following methods:
1119
878
  # * <tt>can_park_car?</tt>
1120
879
  # * <tt>park_car_transition</tt>
1121
880
  # * <tt>park_car</tt>
1122
881
  # * <tt>park_car!</tt>
1123
- #
882
+ #
1124
883
  # The <tt>can_park?</tt> and <tt>park_transition</tt> helpers both take an
1125
884
  # optional set of requirements for determining what transitions are available
1126
885
  # for the current object. These requirements include:
@@ -1130,58 +889,58 @@ module StateMachines
1130
889
  # specified, then this will match any to state.
1131
890
  # * <tt>:guard</tt> - Whether to guard transitions with the if/unless
1132
891
  # conditionals defined for each one. Default is true.
1133
- #
892
+ #
1134
893
  # == Defining transitions
1135
- #
894
+ #
1136
895
  # +event+ requires a block which allows you to define the possible
1137
896
  # transitions that can happen as a result of that event. For example,
1138
- #
897
+ #
1139
898
  # event :park, :stop do
1140
899
  # transition :idling => :parked
1141
900
  # end
1142
- #
901
+ #
1143
902
  # event :first_gear do
1144
903
  # transition :parked => :first_gear, :if => :seatbelt_on?
1145
904
  # transition :parked => same # Allow to loopback if seatbelt is off
1146
905
  # end
1147
- #
906
+ #
1148
907
  # See StateMachines::Event#transition for more information on
1149
908
  # the possible options that can be passed in.
1150
- #
909
+ #
1151
910
  # *Note* that this block is executed within the context of the actual event
1152
911
  # object. As a result, you will not be able to reference any class methods
1153
912
  # on the model without referencing the class itself. For example,
1154
- #
913
+ #
1155
914
  # class Vehicle
1156
915
  # def self.safe_states
1157
916
  # [:parked, :idling, :stalled]
1158
917
  # end
1159
- #
918
+ #
1160
919
  # state_machine do
1161
920
  # event :park do
1162
921
  # transition Vehicle.safe_states => :parked
1163
922
  # end
1164
923
  # end
1165
- # end
1166
- #
924
+ # end
925
+ #
1167
926
  # == Overriding the event method
1168
- #
927
+ #
1169
928
  # By default, this will define an instance method (with the same name as the
1170
929
  # event) that will fire the next possible transition for that. Although the
1171
930
  # +before_transition+, +after_transition+, and +around_transition+ hooks
1172
931
  # allow you to define behavior that gets executed as a result of the event's
1173
932
  # transition, you can also override the event method in order to have a
1174
933
  # little more fine-grained control.
1175
- #
934
+ #
1176
935
  # For example:
1177
- #
936
+ #
1178
937
  # class Vehicle
1179
938
  # state_machine do
1180
939
  # event :park do
1181
940
  # ...
1182
941
  # end
1183
942
  # end
1184
- #
943
+ #
1185
944
  # def park(*)
1186
945
  # take_deep_breath # Executes before the transition (and before_transition hooks) even if no transition is possible
1187
946
  # if result = super # Runs the transition and all before/after/around hooks
@@ -1190,33 +949,33 @@ module StateMachines
1190
949
  # result
1191
950
  # end
1192
951
  # end
1193
- #
952
+ #
1194
953
  # There are a few important things to note here. First, the method
1195
954
  # signature is defined with an unlimited argument list in order to allow
1196
955
  # callers to continue passing arguments that are expected by state_machine.
1197
956
  # For example, it will still allow calls to +park+ with a single parameter
1198
957
  # for skipping the configured action.
1199
- #
958
+ #
1200
959
  # Second, the overridden event method must call +super+ in order to run the
1201
960
  # logic for running the next possible transition. In order to remain
1202
961
  # consistent with other events, the result of +super+ is returned.
1203
- #
962
+ #
1204
963
  # Third, any behavior defined in this method will *not* get executed if
1205
964
  # you're taking advantage of attribute-based event transitions. For example:
1206
- #
965
+ #
1207
966
  # vehicle = Vehicle.new
1208
967
  # vehicle.state_event = 'park'
1209
968
  # vehicle.save
1210
- #
969
+ #
1211
970
  # In this case, the +park+ event will run the before/after/around transition
1212
971
  # hooks and transition the state, but the behavior defined in the overriden
1213
972
  # +park+ method will *not* be executed.
1214
- #
973
+ #
1215
974
  # == Defining additional arguments
1216
- #
975
+ #
1217
976
  # Additional arguments can be passed into events and accessed by transition
1218
977
  # hooks like so:
1219
- #
978
+ #
1220
979
  # class Vehicle
1221
980
  # state_machine do
1222
981
  # after_transition :on => :park do |vehicle, transition|
@@ -1224,157 +983,128 @@ module StateMachines
1224
983
  # ...
1225
984
  # end
1226
985
  # after_transition :on => :park, :do => :take_deep_breath
1227
- #
986
+ #
1228
987
  # event :park do
1229
988
  # ...
1230
989
  # end
1231
- #
990
+ #
1232
991
  # def take_deep_breath(transition)
1233
992
  # kind = *transition.args # :parallel
1234
993
  # ...
1235
994
  # end
1236
995
  # end
1237
996
  # end
1238
- #
997
+ #
1239
998
  # vehicle = Vehicle.new
1240
999
  # vehicle.park(:parallel)
1241
- #
1000
+ #
1242
1001
  # *Remember* that if the last argument is a boolean, it will be used as the
1243
1002
  # +run_action+ parameter to the event action. Using the +park+ action
1244
1003
  # example from above, you can might call it like so:
1245
- #
1004
+ #
1246
1005
  # vehicle.park # => Uses default args and runs machine action
1247
1006
  # vehicle.park(:parallel) # => Specifies the +kind+ argument and runs the machine action
1248
1007
  # vehicle.park(:parallel, false) # => Specifies the +kind+ argument and *skips* the machine action
1249
- #
1008
+ #
1250
1009
  # If you decide to override the +park+ event method *and* define additional
1251
1010
  # arguments, you can do so as shown below:
1252
- #
1011
+ #
1253
1012
  # class Vehicle
1254
1013
  # state_machine do
1255
1014
  # event :park do
1256
1015
  # ...
1257
1016
  # end
1258
1017
  # end
1259
- #
1018
+ #
1260
1019
  # def park(kind = :parallel, *args)
1261
1020
  # take_deep_breath if kind == :parallel
1262
1021
  # super
1263
1022
  # end
1264
1023
  # end
1265
- #
1024
+ #
1266
1025
  # Note that +super+ is called instead of <tt>super(*args)</tt>. This allow
1267
1026
  # the entire arguments list to be accessed by transition callbacks through
1268
1027
  # StateMachines::Transition#args.
1269
- #
1028
+ #
1270
1029
  # === Using matchers
1271
- #
1030
+ #
1272
1031
  # The +all+ / +any+ matchers can be used to easily execute blocks for a
1273
1032
  # group of events. Note, however, that you cannot use these matchers to
1274
1033
  # set configurations for events. Blocks using these matchers can be
1275
1034
  # defined at any point in the state machine and will always get applied to
1276
1035
  # the proper events.
1277
- #
1036
+ #
1278
1037
  # For example:
1279
- #
1038
+ #
1280
1039
  # state_machine :initial => :parked do
1281
1040
  # ...
1282
- #
1041
+ #
1283
1042
  # event all - [:crash] do
1284
1043
  # transition :stalled => :parked
1285
1044
  # end
1286
1045
  # end
1287
- #
1046
+ #
1288
1047
  # == Example
1289
- #
1048
+ #
1290
1049
  # class Vehicle
1291
1050
  # state_machine do
1292
1051
  # # The park, stop, and halt events will all share the given transitions
1293
1052
  # event :park, :stop, :halt do
1294
1053
  # transition [:idling, :backing_up] => :parked
1295
1054
  # end
1296
- #
1055
+ #
1297
1056
  # event :stop do
1298
1057
  # transition :first_gear => :idling
1299
1058
  # end
1300
- #
1059
+ #
1301
1060
  # event :ignite do
1302
1061
  # transition :parked => :idling
1303
1062
  # transition :idling => same # Allow ignite while still idling
1304
1063
  # end
1305
1064
  # end
1306
1065
  # end
1307
- def event(*names, &block)
1308
- options = names.last.is_a?(Hash) ? names.pop : {}
1309
- options.assert_valid_keys(:human_name)
1310
-
1311
- # Store the context so that it can be used for / matched against any event
1312
- # that gets added
1313
- @events.context(names, &block) if block_given?
1314
-
1315
- if names.first.is_a?(Matcher)
1316
- # Add any events referenced in the matcher. When matchers are used,
1317
- # events are not allowed to be configured.
1318
- raise ArgumentError, "Cannot configure events when using matchers (using #{options.inspect})" if options.any?
1319
- events = add_events(names.first.values)
1320
- else
1321
- events = add_events(names)
1322
-
1323
- # Update the configuration for the event(s)
1324
- events.each do |event|
1325
- event.human_name = options[:human_name] if options.include?(:human_name)
1326
-
1327
- # Add any states that may have been referenced within the event
1328
- add_states(event.known_states)
1329
- end
1330
- end
1331
-
1332
- events.length == 1 ? events.first : events
1333
- end
1334
-
1335
- alias_method :on, :event
1336
1066
 
1337
1067
  # Creates a new transition that determines what to change the current state
1338
1068
  # to when an event fires.
1339
- #
1069
+ #
1340
1070
  # == Defining transitions
1341
- #
1071
+ #
1342
1072
  # The options for a new transition uses the Hash syntax to map beginning
1343
1073
  # states to ending states. For example,
1344
- #
1074
+ #
1345
1075
  # transition :parked => :idling, :idling => :first_gear, :on => :ignite
1346
- #
1076
+ #
1347
1077
  # In this case, when the +ignite+ event is fired, this transition will cause
1348
1078
  # the state to be +idling+ if it's current state is +parked+ or +first_gear+
1349
1079
  # if it's current state is +idling+.
1350
- #
1080
+ #
1351
1081
  # To help define these implicit transitions, a set of helpers are available
1352
1082
  # for slightly more complex matching:
1353
1083
  # * <tt>all</tt> - Matches every state in the machine
1354
1084
  # * <tt>all - [:parked, :idling, ...]</tt> - Matches every state except those specified
1355
1085
  # * <tt>any</tt> - An alias for +all+ (matches every state in the machine)
1356
1086
  # * <tt>same</tt> - Matches the same state being transitioned from
1357
- #
1087
+ #
1358
1088
  # See StateMachines::MatcherHelpers for more information.
1359
- #
1089
+ #
1360
1090
  # Examples:
1361
- #
1091
+ #
1362
1092
  # transition all => nil, :on => :ignite # Transitions to nil regardless of the current state
1363
1093
  # transition all => :idling, :on => :ignite # Transitions to :idling regardless of the current state
1364
1094
  # transition all - [:idling, :first_gear] => :idling, :on => :ignite # Transitions every state but :idling and :first_gear to :idling
1365
1095
  # transition nil => :idling, :on => :ignite # Transitions to :idling from the nil state
1366
1096
  # transition :parked => :idling, :on => :ignite # Transitions to :idling if :parked
1367
1097
  # transition [:parked, :stalled] => :idling, :on => :ignite # Transitions to :idling if :parked or :stalled
1368
- #
1098
+ #
1369
1099
  # transition :parked => same, :on => :park # Loops :parked back to :parked
1370
1100
  # transition [:parked, :stalled] => same, :on => [:park, :stall] # Loops either :parked or :stalled back to the same state on the park and stall events
1371
1101
  # transition all - :parked => same, :on => :noop # Loops every state but :parked back to the same state
1372
- #
1102
+ #
1373
1103
  # # Transitions to :idling if :parked, :first_gear if :idling, or :second_gear if :first_gear
1374
1104
  # transition :parked => :idling, :idling => :first_gear, :first_gear => :second_gear, :on => :shift_up
1375
- #
1105
+ #
1376
1106
  # == Verbose transitions
1377
- #
1107
+ #
1378
1108
  # Transitions can also be defined use an explicit set of configuration
1379
1109
  # options:
1380
1110
  # * <tt>:from</tt> - A state or array of states that can be transitioned from.
@@ -1383,24 +1113,24 @@ module StateMachines
1383
1113
  # then the transition will simply loop back (i.e. the state will not change).
1384
1114
  # * <tt>:except_from</tt> - A state or array of states that *cannot* be
1385
1115
  # transitioned from.
1386
- #
1116
+ #
1387
1117
  # These options must be used when defining transitions within the context
1388
1118
  # of a state.
1389
- #
1119
+ #
1390
1120
  # Examples:
1391
- #
1121
+ #
1392
1122
  # transition :to => nil, :on => :park
1393
1123
  # transition :to => :idling, :on => :ignite
1394
1124
  # transition :except_from => [:idling, :first_gear], :to => :idling, :on => :ignite
1395
1125
  # transition :from => nil, :to => :idling, :on => :ignite
1396
1126
  # transition :from => [:parked, :stalled], :to => :idling, :on => :ignite
1397
- #
1127
+ #
1398
1128
  # == Conditions
1399
- #
1129
+ #
1400
1130
  # In addition to the state requirements for each transition, a condition
1401
1131
  # can also be defined to help determine whether that transition is
1402
1132
  # available. These options will work on both the normal and verbose syntax.
1403
- #
1133
+ #
1404
1134
  # Configuration options:
1405
1135
  # * <tt>:if</tt> - A method, proc or string to call to determine if the
1406
1136
  # transition should occur (e.g. :if => :moving?, or :if => lambda {|vehicle| vehicle.speed > 60}).
@@ -1408,39 +1138,30 @@ module StateMachines
1408
1138
  # * <tt>:unless</tt> - A method, proc or string to call to determine if the
1409
1139
  # transition should not occur (e.g. :unless => :stopped?, or :unless => lambda {|vehicle| vehicle.speed <= 60}).
1410
1140
  # The condition should return or evaluate to true or false.
1411
- #
1141
+ #
1412
1142
  # Examples:
1413
- #
1143
+ #
1414
1144
  # transition :parked => :idling, :on => :ignite, :if => :moving?
1415
1145
  # transition :parked => :idling, :on => :ignite, :unless => :stopped?
1416
1146
  # transition :idling => :first_gear, :first_gear => :second_gear, :on => :shift_up, :if => :seatbelt_on?
1417
- #
1147
+ #
1418
1148
  # transition :from => :parked, :to => :idling, :on => ignite, :if => :moving?
1419
1149
  # transition :from => :parked, :to => :idling, :on => ignite, :unless => :stopped?
1420
- #
1150
+ #
1421
1151
  # == Order of operations
1422
- #
1152
+ #
1423
1153
  # Transitions are evaluated in the order in which they're defined. As a
1424
1154
  # result, if more than one transition applies to a given object, then the
1425
1155
  # first transition that matches will be performed.
1426
- def transition(options)
1427
- raise ArgumentError, 'Must specify :on event' unless options[:on]
1428
-
1429
- branches = []
1430
- options = options.dup
1431
- event(*Array(options.delete(:on))) { branches << transition(options) }
1432
-
1433
- branches.length == 1 ? branches.first : branches
1434
- end
1435
1156
 
1436
1157
  # Creates a callback that will be invoked *before* a transition is
1437
1158
  # performed so long as the given requirements match the transition.
1438
- #
1159
+ #
1439
1160
  # == The callback
1440
- #
1161
+ #
1441
1162
  # Callbacks must be defined as either an argument, in the :do option, or
1442
1163
  # as a block. For example,
1443
- #
1164
+ #
1444
1165
  # class Vehicle
1445
1166
  # state_machine do
1446
1167
  # before_transition :set_alarm
@@ -1452,13 +1173,13 @@ module StateMachines
1452
1173
  # ...
1453
1174
  # end
1454
1175
  # end
1455
- #
1176
+ #
1456
1177
  # Notice that the first three callbacks are the same in terms of how the
1457
1178
  # methods to invoke are defined. However, using the <tt>:do</tt> can
1458
1179
  # provide for a more fluid DSL.
1459
- #
1180
+ #
1460
1181
  # In addition, multiple callbacks can be defined like so:
1461
- #
1182
+ #
1462
1183
  # class Vehicle
1463
1184
  # state_machine do
1464
1185
  # before_transition :set_alarm, :lock_doors, all => :parked
@@ -1468,54 +1189,54 @@ module StateMachines
1468
1189
  # end
1469
1190
  # end
1470
1191
  # end
1471
- #
1192
+ #
1472
1193
  # Notice that the different ways of configuring methods can be mixed.
1473
- #
1194
+ #
1474
1195
  # == State requirements
1475
- #
1196
+ #
1476
1197
  # Callbacks can require that the machine be transitioning from and to
1477
1198
  # specific states. These requirements use a Hash syntax to map beginning
1478
1199
  # states to ending states. For example,
1479
- #
1200
+ #
1480
1201
  # before_transition :parked => :idling, :idling => :first_gear, :do => :set_alarm
1481
- #
1202
+ #
1482
1203
  # In this case, the +set_alarm+ callback will only be called if the machine
1483
1204
  # is transitioning from +parked+ to +idling+ or from +idling+ to +parked+.
1484
- #
1205
+ #
1485
1206
  # To help define state requirements, a set of helpers are available for
1486
1207
  # slightly more complex matching:
1487
1208
  # * <tt>all</tt> - Matches every state/event in the machine
1488
1209
  # * <tt>all - [:parked, :idling, ...]</tt> - Matches every state/event except those specified
1489
1210
  # * <tt>any</tt> - An alias for +all+ (matches every state/event in the machine)
1490
1211
  # * <tt>same</tt> - Matches the same state being transitioned from
1491
- #
1212
+ #
1492
1213
  # See StateMachines::MatcherHelpers for more information.
1493
- #
1214
+ #
1494
1215
  # Examples:
1495
- #
1216
+ #
1496
1217
  # before_transition :parked => [:idling, :first_gear], :do => ... # Matches from parked to idling or first_gear
1497
1218
  # before_transition all - [:parked, :idling] => :idling, :do => ... # Matches from every state except parked and idling to idling
1498
1219
  # before_transition all => :parked, :do => ... # Matches all states to parked
1499
1220
  # before_transition any => same, :do => ... # Matches every loopback
1500
- #
1221
+ #
1501
1222
  # == Event requirements
1502
- #
1223
+ #
1503
1224
  # In addition to state requirements, an event requirement can be defined so
1504
1225
  # that the callback is only invoked on specific events using the +on+
1505
1226
  # option. This can also use the same matcher helpers as the state
1506
1227
  # requirements.
1507
- #
1228
+ #
1508
1229
  # Examples:
1509
- #
1230
+ #
1510
1231
  # before_transition :on => :ignite, :do => ... # Matches only on ignite
1511
1232
  # before_transition :on => all - :ignite, :do => ... # Matches on every event except ignite
1512
1233
  # before_transition :parked => :idling, :on => :ignite, :do => ... # Matches from parked to idling on ignite
1513
- #
1234
+ #
1514
1235
  # == Verbose Requirements
1515
- #
1236
+ #
1516
1237
  # Requirements can also be defined using verbose options rather than the
1517
1238
  # implicit Hash syntax and helper methods described above.
1518
- #
1239
+ #
1519
1240
  # Configuration options:
1520
1241
  # * <tt>:from</tt> - One or more states being transitioned from. If none
1521
1242
  # are specified, then all states will match.
@@ -1526,163 +1247,173 @@ module StateMachines
1526
1247
  # * <tt>:except_from</tt> - One or more states *not* being transitioned from
1527
1248
  # * <tt>:except_to</tt> - One more states *not* being transitioned to
1528
1249
  # * <tt>:except_on</tt> - One or more events that *did not* fire the transition
1529
- #
1250
+ #
1530
1251
  # Examples:
1531
- #
1252
+ #
1532
1253
  # before_transition :from => :ignite, :to => :idling, :on => :park, :do => ...
1533
1254
  # before_transition :except_from => :ignite, :except_to => :idling, :except_on => :park, :do => ...
1534
- #
1255
+ #
1535
1256
  # == Conditions
1536
- #
1257
+ #
1537
1258
  # In addition to the state/event requirements, a condition can also be
1538
1259
  # defined to help determine whether the callback should be invoked.
1539
- #
1260
+ #
1540
1261
  # Configuration options:
1541
1262
  # * <tt>:if</tt> - A method, proc or string to call to determine if the
1542
1263
  # callback should occur (e.g. :if => :allow_callbacks, or
1543
1264
  # :if => lambda {|user| user.signup_step > 2}). The method, proc or string
1544
- # should return or evaluate to a true or false value.
1265
+ # should return or evaluate to a true or false value.
1545
1266
  # * <tt>:unless</tt> - A method, proc or string to call to determine if the
1546
1267
  # callback should not occur (e.g. :unless => :skip_callbacks, or
1547
1268
  # :unless => lambda {|user| user.signup_step <= 2}). The method, proc or
1548
- # string should return or evaluate to a true or false value.
1549
- #
1269
+ # string should return or evaluate to a true or false value.
1270
+ #
1550
1271
  # Examples:
1551
- #
1272
+ #
1552
1273
  # before_transition :parked => :idling, :if => :moving?, :do => ...
1553
1274
  # before_transition :on => :ignite, :unless => :seatbelt_on?, :do => ...
1554
- #
1275
+ #
1555
1276
  # == Accessing the transition
1556
- #
1277
+ #
1557
1278
  # In addition to passing the object being transitioned, the actual
1558
1279
  # transition describing the context (e.g. event, from, to) can be accessed
1559
1280
  # as well. This additional argument is only passed if the callback allows
1560
1281
  # for it.
1561
- #
1282
+ #
1562
1283
  # For example,
1563
- #
1284
+ #
1564
1285
  # class Vehicle
1565
1286
  # # Only specifies one parameter (the object being transitioned)
1566
1287
  # before_transition all => :parked do |vehicle|
1567
1288
  # vehicle.set_alarm
1568
1289
  # end
1569
- #
1290
+ #
1570
1291
  # # Specifies 2 parameters (object being transitioned and actual transition)
1571
1292
  # before_transition all => :parked do |vehicle, transition|
1572
1293
  # vehicle.set_alarm(transition)
1573
1294
  # end
1574
1295
  # end
1575
- #
1296
+ #
1576
1297
  # *Note* that the object in the callback will only be passed in as an
1577
1298
  # argument if callbacks are configured to *not* be bound to the object
1578
1299
  # involved. This is the default and may change on a per-integration basis.
1579
- #
1300
+ #
1580
1301
  # See StateMachines::Transition for more information about the
1581
1302
  # attributes available on the transition.
1582
- #
1303
+ #
1583
1304
  # == Usage with delegates
1584
- #
1305
+ #
1585
1306
  # As noted above, state_machine uses the callback method's argument list
1586
1307
  # arity to determine whether to include the transition in the method call.
1587
1308
  # If you're using delegates, such as those defined in ActiveSupport or
1588
1309
  # Forwardable, the actual arity of the delegated method gets masked. This
1589
1310
  # means that callbacks which reference delegates will always get passed the
1590
1311
  # transition as an argument. For example:
1591
- #
1312
+ #
1592
1313
  # class Vehicle
1593
1314
  # extend Forwardable
1594
1315
  # delegate :refresh => :dashboard
1595
- #
1316
+ #
1596
1317
  # state_machine do
1597
1318
  # before_transition :refresh
1598
1319
  # ...
1599
1320
  # end
1600
- #
1321
+ #
1601
1322
  # def dashboard
1602
1323
  # @dashboard ||= Dashboard.new
1603
1324
  # end
1604
1325
  # end
1605
- #
1326
+ #
1606
1327
  # class Dashboard
1607
1328
  # def refresh(transition)
1608
1329
  # # ...
1609
1330
  # end
1610
1331
  # end
1611
- #
1332
+ #
1612
1333
  # In the above example, <tt>Dashboard#refresh</tt> *must* defined a
1613
1334
  # +transition+ argument. Otherwise, an +ArgumentError+ exception will get
1614
1335
  # raised. The only way around this is to avoid the use of delegates and
1615
1336
  # manually define the delegate method so that the correct arity is used.
1616
- #
1337
+ #
1617
1338
  # == Examples
1618
- #
1339
+ #
1619
1340
  # Below is an example of a class with one state machine and various types
1620
1341
  # of +before+ transitions defined for it:
1621
- #
1342
+ #
1622
1343
  # class Vehicle
1623
1344
  # state_machine do
1624
1345
  # # Before all transitions
1625
1346
  # before_transition :update_dashboard
1626
- #
1347
+ #
1627
1348
  # # Before specific transition:
1628
1349
  # before_transition [:first_gear, :idling] => :parked, :on => :park, :do => :take_off_seatbelt
1629
- #
1350
+ #
1630
1351
  # # With conditional callback:
1631
1352
  # before_transition all => :parked, :do => :take_off_seatbelt, :if => :seatbelt_on?
1632
- #
1353
+ #
1633
1354
  # # Using helpers:
1634
1355
  # before_transition all - :stalled => same, :on => any - :crash, :do => :update_dashboard
1635
1356
  # ...
1636
1357
  # end
1637
1358
  # end
1638
- #
1359
+ #
1639
1360
  # As can be seen, any number of transitions can be created using various
1640
1361
  # combinations of configuration options.
1641
- def before_transition(*args, &block)
1642
- options = (args.last.is_a?(Hash) ? args.pop : {})
1643
- options[:do] = args if args.any?
1644
- add_callback(:before, options, &block)
1362
+ def before_transition(*args, **options, &)
1363
+ # Extract legacy positional arguments and merge with keyword options
1364
+ parsed_options = parse_callback_arguments(args, options)
1365
+
1366
+ # Only validate callback-specific options, not state transition requirements
1367
+ callback_options = parsed_options.slice(:do, :if, :unless, :bind_to_object, :terminator)
1368
+ StateMachines::OptionsValidator.assert_valid_keys!(callback_options, :do, :if, :unless, :bind_to_object, :terminator)
1369
+
1370
+ add_callback(:before, parsed_options, &)
1645
1371
  end
1646
1372
 
1647
1373
  # Creates a callback that will be invoked *after* a transition is
1648
1374
  # performed so long as the given requirements match the transition.
1649
- #
1375
+ #
1650
1376
  # See +before_transition+ for a description of the possible configurations
1651
1377
  # for defining callbacks.
1652
- def after_transition(*args, &block)
1653
- options = (args.last.is_a?(Hash) ? args.pop : {})
1654
- options[:do] = args if args.any?
1655
- add_callback(:after, options, &block)
1378
+ def after_transition(*args, **options, &)
1379
+ # Extract legacy positional arguments and merge with keyword options
1380
+ parsed_options = parse_callback_arguments(args, options)
1381
+
1382
+ # Only validate callback-specific options, not state transition requirements
1383
+ callback_options = parsed_options.slice(:do, :if, :unless, :bind_to_object, :terminator)
1384
+ StateMachines::OptionsValidator.assert_valid_keys!(callback_options, :do, :if, :unless, :bind_to_object, :terminator)
1385
+
1386
+ add_callback(:after, parsed_options, &)
1656
1387
  end
1657
1388
 
1658
1389
  # Creates a callback that will be invoked *around* a transition so long as
1659
1390
  # the given requirements match the transition.
1660
- #
1391
+ #
1661
1392
  # == The callback
1662
- #
1393
+ #
1663
1394
  # Around callbacks wrap transitions, executing code both before and after.
1664
1395
  # These callbacks are defined in the exact same manner as before / after
1665
1396
  # callbacks with the exception that the transition must be yielded to in
1666
1397
  # order to finish running it.
1667
- #
1398
+ #
1668
1399
  # If defining +around+ callbacks using blocks, you must yield within the
1669
1400
  # transition by directly calling the block (since yielding is not allowed
1670
1401
  # within blocks).
1671
- #
1402
+ #
1672
1403
  # For example,
1673
- #
1404
+ #
1674
1405
  # class Vehicle
1675
1406
  # state_machine do
1676
1407
  # around_transition do |block|
1677
1408
  # Benchmark.measure { block.call }
1678
1409
  # end
1679
- #
1410
+ #
1680
1411
  # around_transition do |vehicle, block|
1681
1412
  # logger.info "vehicle was #{state}..."
1682
1413
  # block.call
1683
1414
  # logger.info "...and is now #{state}"
1684
1415
  # end
1685
- #
1416
+ #
1686
1417
  # around_transition do |vehicle, transition, block|
1687
1418
  # logger.info "before #{transition.event}: #{vehicle.state}"
1688
1419
  # block.call
@@ -1690,73 +1421,78 @@ module StateMachines
1690
1421
  # end
1691
1422
  # end
1692
1423
  # end
1693
- #
1424
+ #
1694
1425
  # Notice that referencing the block is similar to doing so within an
1695
1426
  # actual method definition in that it is always the last argument.
1696
- #
1427
+ #
1697
1428
  # On the other hand, if you're defining +around+ callbacks using method
1698
1429
  # references, you can yield like normal:
1699
- #
1430
+ #
1700
1431
  # class Vehicle
1701
1432
  # state_machine do
1702
1433
  # around_transition :benchmark
1703
1434
  # ...
1704
1435
  # end
1705
- #
1436
+ #
1706
1437
  # def benchmark
1707
1438
  # Benchmark.measure { yield }
1708
1439
  # end
1709
1440
  # end
1710
- #
1441
+ #
1711
1442
  # See +before_transition+ for a description of the possible configurations
1712
1443
  # for defining callbacks.
1713
- def around_transition(*args, &block)
1714
- options = (args.last.is_a?(Hash) ? args.pop : {})
1715
- options[:do] = args if args.any?
1716
- add_callback(:around, options, &block)
1444
+ def around_transition(*args, **options, &)
1445
+ # Extract legacy positional arguments and merge with keyword options
1446
+ parsed_options = parse_callback_arguments(args, options)
1447
+
1448
+ # Only validate callback-specific options, not state transition requirements
1449
+ callback_options = parsed_options.slice(:do, :if, :unless, :bind_to_object, :terminator)
1450
+ StateMachines::OptionsValidator.assert_valid_keys!(callback_options, :do, :if, :unless, :bind_to_object, :terminator)
1451
+
1452
+ add_callback(:around, parsed_options, &)
1717
1453
  end
1718
1454
 
1719
1455
  # Creates a callback that will be invoked *after* a transition failures to
1720
1456
  # be performed so long as the given requirements match the transition.
1721
- #
1457
+ #
1722
1458
  # See +before_transition+ for a description of the possible configurations
1723
1459
  # for defining callbacks. *Note* however that you cannot define the state
1724
1460
  # requirements in these callbacks. You may only define event requirements.
1725
- #
1461
+ #
1726
1462
  # = The callback
1727
- #
1463
+ #
1728
1464
  # Failure callbacks get invoked whenever an event fails to execute. This
1729
1465
  # can happen when no transition is available, a +before+ callback halts
1730
1466
  # execution, or the action associated with this machine fails to succeed.
1731
1467
  # In any of these cases, any failure callback that matches the attempted
1732
1468
  # transition will be run.
1733
- #
1469
+ #
1734
1470
  # For example,
1735
- #
1471
+ #
1736
1472
  # class Vehicle
1737
1473
  # state_machine do
1738
1474
  # after_failure do |vehicle, transition|
1739
1475
  # logger.error "vehicle #{vehicle} failed to transition on #{transition.event}"
1740
1476
  # end
1741
- #
1477
+ #
1742
1478
  # after_failure :on => :ignite, :do => :log_ignition_failure
1743
- #
1479
+ #
1744
1480
  # ...
1745
1481
  # end
1746
1482
  # end
1747
- def after_failure(*args, &block)
1748
- options = (args.last.is_a?(Hash) ? args.pop : {})
1749
- options[:do] = args if args.any?
1750
- options.assert_valid_keys(:on, :do, :if, :unless)
1483
+ def after_failure(*args, **options, &)
1484
+ # Extract legacy positional arguments and merge with keyword options
1485
+ parsed_options = parse_callback_arguments(args, options)
1486
+ StateMachines::OptionsValidator.assert_valid_keys!(parsed_options, :on, :do, :if, :unless)
1751
1487
 
1752
- add_callback(:failure, options, &block)
1488
+ add_callback(:failure, parsed_options, &)
1753
1489
  end
1754
1490
 
1755
1491
  # Generates a list of the possible transition sequences that can be run on
1756
1492
  # the given object. These paths can reveal all of the possible states and
1757
1493
  # events that can be encountered in the object's state machine based on the
1758
1494
  # object's current state.
1759
- #
1495
+ #
1760
1496
  # Configuration options:
1761
1497
  # * +from+ - The initial state to start all paths from. By default, this
1762
1498
  # is the object's current state.
@@ -1767,31 +1503,31 @@ module StateMachines
1767
1503
  # state (if specified) is reached. If this is enabled, then paths can
1768
1504
  # continue even after reaching the target state; they will stop when
1769
1505
  # reaching the target state a second time.
1770
- #
1506
+ #
1771
1507
  # *Note* that the object is never modified when the list of paths is
1772
1508
  # generated.
1773
- #
1509
+ #
1774
1510
  # == Examples
1775
- #
1511
+ #
1776
1512
  # class Vehicle
1777
1513
  # state_machine :initial => :parked do
1778
1514
  # event :ignite do
1779
1515
  # transition :parked => :idling
1780
1516
  # end
1781
- #
1517
+ #
1782
1518
  # event :shift_up do
1783
1519
  # transition :idling => :first_gear, :first_gear => :second_gear
1784
1520
  # end
1785
- #
1521
+ #
1786
1522
  # event :shift_down do
1787
1523
  # transition :second_gear => :first_gear, :first_gear => :idling
1788
1524
  # end
1789
1525
  # end
1790
1526
  # end
1791
- #
1527
+ #
1792
1528
  # vehicle = Vehicle.new # => #<Vehicle:0xb7c27024 @state="parked">
1793
1529
  # vehicle.state # => "parked"
1794
- #
1530
+ #
1795
1531
  # vehicle.state_paths
1796
1532
  # # => [
1797
1533
  # # [#<StateMachines::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>,
@@ -1799,37 +1535,33 @@ module StateMachines
1799
1535
  # # #<StateMachines::Transition attribute=:state event=:shift_up from="first_gear" from_name=:first_gear to="second_gear" to_name=:second_gear>,
1800
1536
  # # #<StateMachines::Transition attribute=:state event=:shift_down from="second_gear" from_name=:second_gear to="first_gear" to_name=:first_gear>,
1801
1537
  # # #<StateMachines::Transition attribute=:state event=:shift_down from="first_gear" from_name=:first_gear to="idling" to_name=:idling>],
1802
- # #
1538
+ # #
1803
1539
  # # [#<StateMachines::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>,
1804
1540
  # # #<StateMachines::Transition attribute=:state event=:shift_up from="idling" from_name=:idling to="first_gear" to_name=:first_gear>,
1805
1541
  # # #<StateMachines::Transition attribute=:state event=:shift_down from="first_gear" from_name=:first_gear to="idling" to_name=:idling>]
1806
1542
  # # ]
1807
- #
1543
+ #
1808
1544
  # vehicle.state_paths(:from => :parked, :to => :second_gear)
1809
1545
  # # => [
1810
1546
  # # [#<StateMachines::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>,
1811
1547
  # # #<StateMachines::Transition attribute=:state event=:shift_up from="idling" from_name=:idling to="first_gear" to_name=:first_gear>,
1812
1548
  # # #<StateMachines::Transition attribute=:state event=:shift_up from="first_gear" from_name=:first_gear to="second_gear" to_name=:second_gear>]
1813
1549
  # # ]
1814
- #
1550
+ #
1815
1551
  # In addition to getting the possible paths that can be accessed, you can
1816
1552
  # also get summary information about the states / events that can be
1817
1553
  # accessed at some point along one of the paths. For example:
1818
- #
1554
+ #
1819
1555
  # # Get the list of states that can be accessed from the current state
1820
1556
  # vehicle.state_paths.to_states # => [:idling, :first_gear, :second_gear]
1821
- #
1557
+ #
1822
1558
  # # Get the list of events that can be accessed from the current state
1823
1559
  # vehicle.state_paths.events # => [:ignite, :shift_up, :shift_down]
1824
- def paths_for(object, requirements = {})
1825
- PathCollection.new(object, self, requirements)
1826
- end
1827
1560
 
1828
1561
  # Marks the given object as invalid with the given message.
1829
- #
1562
+ #
1830
1563
  # By default, this is a no-op.
1831
- def invalidate(_object, _attribute, _message, _values = [])
1832
- end
1564
+ def invalidate(_object, _attribute, _message, _values = []); end
1833
1565
 
1834
1566
  # Gets a description of the errors for the given object. This is used to
1835
1567
  # provide more detailed information when an InvalidTransition exception is
@@ -1839,328 +1571,63 @@ module StateMachines
1839
1571
  end
1840
1572
 
1841
1573
  # Resets any errors previously added when invalidating the given object.
1842
- #
1574
+ #
1843
1575
  # By default, this is a no-op.
1844
- def reset(_object)
1845
- end
1576
+ def reset(_object); end
1846
1577
 
1847
1578
  # Generates the message to use when invalidating the given object after
1848
1579
  # failing to transition on a specific event
1849
1580
  def generate_message(name, values = [])
1850
- message = (@messages[name] || self.class.default_messages[name])
1581
+ message = @messages[name] || self.class.default_messages[name]
1851
1582
 
1852
1583
  # Check whether there are actually any values to interpolate to avoid
1853
1584
  # any warnings
1854
1585
  if message.scan(/%./).any? { |match| match != '%%' }
1855
- message % values.map { |value| value.last }
1586
+ message % values.map(&:last)
1856
1587
  else
1857
1588
  message
1858
1589
  end
1859
1590
  end
1860
1591
 
1861
1592
  # Runs a transaction, rolling back any changes if the yielded block fails.
1862
- #
1593
+ #
1863
1594
  # This is only applicable to integrations that involve databases. By
1864
1595
  # default, this will not run any transactions since the changes aren't
1865
1596
  # taking place within the context of a database.
1866
- def within_transaction(object)
1597
+ def within_transaction(object, &)
1867
1598
  if use_transactions
1868
- transaction(object) { yield }
1599
+ transaction(object, &)
1869
1600
  else
1870
1601
  yield
1871
1602
  end
1872
1603
  end
1873
1604
 
1605
+ def renderer
1606
+ self.class.renderer
1607
+ end
1874
1608
 
1875
- def draw(*)
1876
- fail NotImplementedError
1609
+ def draw(**)
1610
+ renderer.draw_machine(self, **)
1877
1611
  end
1878
1612
 
1879
1613
  # Determines whether an action hook was defined for firing attribute-based
1880
1614
  # event transitions when the configured action gets called.
1881
1615
  def action_hook?(self_only = false)
1882
- @action_hook_defined || !self_only && owner_class.state_machines.any? { |name, machine| machine.action == action && machine != self && machine.action_hook?(true) }
1616
+ @action_hook_defined || (!self_only && owner_class.state_machines.any? { |_name, machine| machine.action == action && machine != self && machine.action_hook?(true) })
1883
1617
  end
1884
1618
 
1885
1619
  protected
1886
- # Runs additional initialization hooks. By default, this is a no-op.
1887
- def after_initialize
1888
- end
1889
-
1890
- # Looks up other machines that have been defined in the owner class and
1891
- # are targeting the same attribute as this machine. When accessing
1892
- # sibling machines, they will be automatically copied for the current
1893
- # class if they haven't been already. This ensures that any configuration
1894
- # changes made to the sibling machines only affect this class and not any
1895
- # base class that may have originally defined the machine.
1896
- def sibling_machines
1897
- owner_class.state_machines.inject([]) do |machines, (name, machine)|
1898
- if machine.attribute == attribute && machine != self
1899
- machines << (owner_class.state_machine(name) {})
1900
- end
1901
- machines
1902
- end
1903
- end
1904
-
1905
- # Determines if the machine's attribute needs to be initialized. This
1906
- # will only be true if the machine's attribute is blank.
1907
- def initialize_state?(object)
1908
- value = read(object, :state)
1909
- (value.nil? || value.respond_to?(:empty?) && value.empty?) && !states[value, :value]
1910
- end
1911
-
1912
- # Adds helper methods for interacting with the state machine, including
1913
- # for states, events, and transitions
1914
- def define_helpers
1915
- define_state_accessor
1916
- define_state_predicate
1917
- define_event_helpers
1918
- define_path_helpers
1919
- define_action_helpers if define_action_helpers?
1920
- define_name_helpers
1921
- end
1922
-
1923
- # Defines the initial values for state machine attributes. Static values
1924
- # are set prior to the original initialize method and dynamic values are
1925
- # set *after* the initialize method in case it is dependent on it.
1926
- def define_state_initializer
1927
- define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
1928
- def initialize(*)
1929
- self.class.state_machines.initialize_states(self) { super }
1930
- end
1931
- end_eval
1932
- end
1933
-
1934
- # Adds reader/writer methods for accessing the state attribute
1935
- def define_state_accessor
1936
- attribute = self.attribute
1937
-
1938
- @helper_modules[:instance].class_eval { attr_reader attribute } unless owner_class_ancestor_has_method?(:instance, attribute)
1939
- @helper_modules[:instance].class_eval { attr_writer attribute } unless owner_class_ancestor_has_method?(:instance, "#{attribute}=")
1940
- end
1941
-
1942
- # Adds predicate method to the owner class for determining the name of the
1943
- # current state
1944
- def define_state_predicate
1945
- call_super = !!owner_class_ancestor_has_method?(:instance, "#{name}?")
1946
- define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
1947
- def #{name}?(*args)
1948
- args.empty? && (#{call_super} || defined?(super)) ? super : self.class.state_machine(#{name.inspect}).states.matches?(self, *args)
1949
- end
1950
- end_eval
1951
- end
1952
-
1953
- # Adds helper methods for getting information about this state machine's
1954
- # events
1955
- def define_event_helpers
1956
- # Gets the events that are allowed to fire on the current object
1957
- define_helper(:instance, attribute(:events)) do |machine, object, *args|
1958
- machine.events.valid_for(object, *args).map { |event| event.name }
1959
- end
1960
-
1961
- # Gets the next possible transitions that can be run on the current
1962
- # object
1963
- define_helper(:instance, attribute(:transitions)) do |machine, object, *args|
1964
- machine.events.transitions_for(object, *args)
1965
- end
1966
-
1967
- # Fire an arbitrary event for this machine
1968
- define_helper(:instance, "fire_#{attribute(:event)}") do |machine, object, event, *args|
1969
- machine.events.fetch(event).fire(object, *args)
1970
- end
1971
-
1972
- # Add helpers for tracking the event / transition to invoke when the
1973
- # action is called
1974
- if action
1975
- event_attribute = attribute(:event)
1976
- define_helper(:instance, event_attribute) do |machine, object|
1977
- # Interpret non-blank events as present
1978
- event = machine.read(object, :event, true)
1979
- event && !(event.respond_to?(:empty?) && event.empty?) ? event.to_sym : nil
1980
- end
1981
1620
 
1982
- # A roundabout way of writing the attribute is used here so that
1983
- # integrations can hook into this modification
1984
- define_helper(:instance, "#{event_attribute}=") do |machine, object, value|
1985
- machine.write(object, :event, value, true)
1986
- end
1987
-
1988
- event_transition_attribute = attribute(:event_transition)
1989
- define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
1990
- protected; attr_accessor #{event_transition_attribute.inspect}
1991
- end_eval
1992
- end
1993
- end
1994
-
1995
- # Adds helper methods for getting information about this state machine's
1996
- # available transition paths
1997
- def define_path_helpers
1998
- # Gets the paths of transitions available to the current object
1999
- define_helper(:instance, attribute(:paths)) do |machine, object, *args|
2000
- machine.paths_for(object, *args)
2001
- end
2002
- end
2003
-
2004
- # Determines whether action helpers should be defined for this machine.
2005
- # This is only true if there is an action configured and no other machines
2006
- # have process this same configuration already.
2007
- def define_action_helpers?
2008
- action && !owner_class.state_machines.any? { |name, machine| machine.action == action && machine != self }
2009
- end
2010
-
2011
- # Adds helper methods for automatically firing events when an action
2012
- # is invoked
2013
- def define_action_helpers
2014
- if action_hook
2015
- @action_hook_defined = true
2016
- define_action_hook
2017
- end
2018
- end
2019
-
2020
- # Hooks directly into actions by defining the same method in an included
2021
- # module. As a result, when the action gets invoked, any state events
2022
- # defined for the object will get run. Method visibility is preserved.
2023
- def define_action_hook
2024
- action_hook = self.action_hook
2025
- action = self.action
2026
- private_action_hook = owner_class.private_method_defined?(action_hook)
2027
-
2028
- # Only define helper if it hasn't
2029
- define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
2030
- def #{action_hook}(*)
2031
- self.class.state_machines.transitions(self, #{action.inspect}).perform { super }
2032
- end
2033
-
2034
- private #{action_hook.inspect} if #{private_action_hook}
2035
- end_eval
2036
- end
2037
-
2038
- # The method to hook into for triggering transitions when invoked. By
2039
- # default, this is the action configured for the machine.
2040
- #
2041
- # Since the default hook technique relies on module inheritance, the
2042
- # action must be defined in an ancestor of the owner classs in order for
2043
- # it to be the action hook.
2044
- def action_hook
2045
- action && owner_class_ancestor_has_method?(:instance, action) ? action : nil
2046
- end
1621
+ # Runs additional initialization hooks. By default, this is a no-op.
1622
+ def after_initialize; end
2047
1623
 
2048
1624
  # Determines whether there's already a helper method defined within the
2049
1625
  # given scope. This is true only if one of the owner's ancestors defines
2050
1626
  # the method and is further along in the ancestor chain than this
2051
1627
  # machine's helper module.
2052
- def owner_class_ancestor_has_method?(scope, method)
2053
- return false unless owner_class_has_method?(scope, method)
2054
-
2055
- superclasses = owner_class.ancestors[1..-1].select { |ancestor| ancestor.is_a?(Class) }
2056
-
2057
- if scope == :class
2058
- current = owner_class.singleton_class
2059
- superclass = superclasses.first
2060
- else
2061
- current = owner_class
2062
- superclass = owner_class.superclass
2063
- end
2064
-
2065
- # Generate the list of modules that *only* occur in the owner class, but
2066
- # were included *prior* to the helper modules, in addition to the
2067
- # superclasses
2068
- ancestors = current.ancestors - superclass.ancestors + superclasses
2069
- ancestors = ancestors[ancestors.index(@helper_modules[scope])..-1].reverse
2070
-
2071
- # Search for for the first ancestor that defined this method
2072
- ancestors.detect do |ancestor|
2073
- ancestor = ancestor.singleton_class if scope == :class && ancestor.is_a?(Class)
2074
- ancestor.method_defined?(method) || ancestor.private_method_defined?(method)
2075
- end
2076
- end
2077
-
2078
- def owner_class_has_method?(scope, method)
2079
- target = scope == :class ? owner_class.singleton_class : owner_class
2080
- target.method_defined?(method) || target.private_method_defined?(method)
2081
- end
2082
-
2083
- # Adds helper methods for accessing naming information about states and
2084
- # events on the owner class
2085
- def define_name_helpers
2086
- # Gets the humanized version of a state
2087
- define_helper(:class, "human_#{attribute(:name)}") do |machine, klass, state|
2088
- machine.states.fetch(state).human_name(klass)
2089
- end
2090
-
2091
- # Gets the humanized version of an event
2092
- define_helper(:class, "human_#{attribute(:event_name)}") do |machine, klass, event|
2093
- machine.events.fetch(event).human_name(klass)
2094
- end
2095
-
2096
- # Gets the state name for the current value
2097
- define_helper(:instance, attribute(:name)) do |machine, object|
2098
- machine.states.match!(object).name
2099
- end
2100
-
2101
- # Gets the human state name for the current value
2102
- define_helper(:instance, "human_#{attribute(:name)}") do |machine, object|
2103
- machine.states.match!(object).human_name(object.class)
2104
- end
2105
- end
2106
-
2107
- # Defines the with/without scope helpers for this attribute. Both the
2108
- # singular and plural versions of the attribute are defined for each
2109
- # scope helper. A custom plural can be specified if it cannot be
2110
- # automatically determined by either calling +pluralize+ on the attribute
2111
- # name or adding an "s" to the end of the name.
2112
- def define_scopes(custom_plural = nil)
2113
- plural = custom_plural || pluralize(name)
2114
-
2115
- [:with, :without].each do |kind|
2116
- [name, plural].map { |s| s.to_s }.uniq.each do |suffix|
2117
- method = "#{kind}_#{suffix}"
2118
-
2119
- if scope = send("create_#{kind}_scope", method)
2120
- # Converts state names to their corresponding values so that they
2121
- # can be looked up properly
2122
- define_helper(:class, method) do |machine, klass, *states|
2123
- run_scope(scope, machine, klass, states)
2124
- end
2125
- end
2126
- end
2127
- end
2128
- end
2129
-
2130
- # Generates the results for the given scope based on one or more states to
2131
- # filter by
2132
- def run_scope(scope, machine, klass, states)
2133
- values = states.flatten.map { |state| machine.states.fetch(state).value }
2134
- scope.call(klass, values)
2135
- end
2136
-
2137
- # Pluralizes the given word using #pluralize (if available) or simply
2138
- # adding an "s" to the end of the word
2139
- def pluralize(word)
2140
- word = word.to_s
2141
- if word.respond_to?(:pluralize)
2142
- word.pluralize
2143
- else
2144
- "#{name}s"
2145
- end
2146
- end
2147
-
2148
- # Creates a scope for finding objects *with* a particular value or values
2149
- # for the attribute.
2150
- #
2151
- # By default, this is a no-op.
2152
- def create_with_scope(name)
2153
- end
2154
-
2155
- # Creates a scope for finding objects *without* a particular value or
2156
- # values for the attribute.
2157
- #
2158
- # By default, this is a no-op.
2159
- def create_without_scope(name)
2160
- end
2161
1628
 
2162
1629
  # Always yields
2163
- def transaction(object)
1630
+ def transaction(_object)
2164
1631
  yield
2165
1632
  end
2166
1633
 
@@ -2175,60 +1642,5 @@ module StateMachines
2175
1642
  def owner_class_attribute_default_matches?(state)
2176
1643
  state.matches?(owner_class_attribute_default)
2177
1644
  end
2178
-
2179
- # Updates this machine based on the configuration of other machines in the
2180
- # owner class that share the same target attribute.
2181
- def add_sibling_machine_configs
2182
- # Add existing states
2183
- sibling_machines.each do |machine|
2184
- machine.states.each { |state| states << state unless states[state.name] }
2185
- end
2186
- end
2187
-
2188
- # Adds a new transition callback of the given type.
2189
- def add_callback(type, options, &block)
2190
- callbacks[type == :around ? :before : type] << callback = Callback.new(type, options, &block)
2191
- add_states(callback.known_states)
2192
- callback
2193
- end
2194
-
2195
- # Tracks the given set of states in the list of all known states for
2196
- # this machine
2197
- def add_states(new_states)
2198
- new_states.map do |new_state|
2199
- # Check for other states that use a different class type for their name.
2200
- # This typically prevents string / symbol misuse.
2201
- if new_state && conflict = states.detect { |state| state.name && state.name.class != new_state.class }
2202
- raise ArgumentError, "#{new_state.inspect} state defined as #{new_state.class}, #{conflict.name.inspect} defined as #{conflict.name.class}; all states must be consistent"
2203
- end
2204
-
2205
- unless state = states[new_state]
2206
- states << state = State.new(self, new_state)
2207
-
2208
- # Copy states over to sibling machines
2209
- sibling_machines.each { |machine| machine.states << state }
2210
- end
2211
-
2212
- state
2213
- end
2214
- end
2215
-
2216
- # Tracks the given set of events in the list of all known events for
2217
- # this machine
2218
- def add_events(new_events)
2219
- new_events.map do |new_event|
2220
- # Check for other states that use a different class type for their name.
2221
- # This typically prevents string / symbol misuse.
2222
- if conflict = events.detect { |event| event.name.class != new_event.class }
2223
- raise ArgumentError, "#{new_event.inspect} event defined as #{new_event.class}, #{conflict.name.inspect} defined as #{conflict.name.class}; all events must be consistent"
2224
- end
2225
-
2226
- unless event = events[new_event]
2227
- events << event = Event.new(self, new_event)
2228
- end
2229
-
2230
- event
2231
- end
2232
- end
2233
1645
  end
2234
1646
  end