state_machines 0.5.0 → 0.50.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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 +146 -86
  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 +197 -39
  17. data/lib/state_machines/event.rb +77 -58
  18. data/lib/state_machines/event_collection.rb +49 -39
  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 +3 -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 +97 -0
  27. data/lib/state_machines/machine/configuration.rb +134 -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 +425 -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 +896 -0
  52. data/lib/state_machines/transition.rb +215 -199
  53. data/lib/state_machines/transition_collection.rb +187 -170
  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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: '029961fc61666a778298a5b6e5b5b31802b8710f'
4
- data.tar.gz: d9e00ff04cbffbaf522e82b40e76aeb9c9ab3564
2
+ SHA256:
3
+ metadata.gz: 247ae1ee6fa7beb6ac29a68dfd400ea41a3923b973d842a5adb2ef78960b3266
4
+ data.tar.gz: 7240a29d3d87740ade0d0197e9c4c0192176023f23e453d1ba659e8be905afef
5
5
  SHA512:
6
- metadata.gz: 52f6b57620f17c661585a1bccf2340d46be80366f2a7de609c41033ff46024cc62fc17814279267b26de65974612f149c0a057b47f2396e45f842c249742c9cd
7
- data.tar.gz: b8bbe1923532edffd936139272782d6053b0ab8f1b338eaf61f805fbbc2a3b1c284bd523c35a375fc40fecb6f4948cb4aabcfc72c0fda5ded46959557a20b1cc
6
+ metadata.gz: 26e8c0a197cdc254c4157a57e3bcfabfef87240280555ae623d993e30ccb68ba6433e911da171f60beb0e756af54aa60ff18395c9d49c6719a06b22bd895bdca
7
+ data.tar.gz: 9dfbdceba5b428f415dfeeba91995ab52640282b2b6c2fb92dea266c97677263331706e6b37f09a6280dc3f578da27dae1f5ade628a655342e46c565ddbe05db
data/LICENSE.txt CHANGED
@@ -1,5 +1,5 @@
1
1
  Copyright (c) 2006-2012 Aaron Pfeifer
2
- Copyright (c) 2014-2015 Abdelkader Boudih
2
+ Copyright (c) 2014-2023 Abdelkader Boudih
3
3
 
4
4
  MIT License
5
5
 
data/README.md CHANGED
@@ -1,5 +1,5 @@
1
- [![Build Status](https://travis-ci.org/state-machines/state_machines.svg?branch=master)](https://travis-ci.org/state-machines/state_machines)
2
- [![Code Climate](https://codeclimate.com/github/state-machines/state_machines.svg)](https://codeclimate.com/github/state-machines/state_machines)
1
+ ![Build Status](https://github.com/state-machines/state_machines/actions/workflows/ruby.yml/badge.svg)
2
+
3
3
  # State Machines
4
4
 
5
5
  State Machines adds support for creating state machines for attributes on any Ruby class.
@@ -10,15 +10,21 @@ State Machines adds support for creating state machines for attributes on any Ru
10
10
 
11
11
  Add this line to your application's Gemfile:
12
12
 
13
- gem 'state_machines'
13
+ ```ruby
14
+ gem 'state_machines'
15
+ ```
14
16
 
15
17
  And then execute:
16
18
 
17
- $ bundle
19
+ ```sh
20
+ bundle
21
+ ```
18
22
 
19
23
  Or install it yourself as:
20
24
 
21
- $ gem install state_machines
25
+ ```sh
26
+ gem install state_machines
27
+ ```
22
28
 
23
29
  ## Usage
24
30
 
@@ -30,6 +36,8 @@ Below is an example of many of the features offered by this plugin, including:
30
36
  * Namespaced states
31
37
  * Transition callbacks
32
38
  * Conditional transitions
39
+ * Coordinated state management guards
40
+ * Asynchronous state machines (async: true)
33
41
  * State-driven instance behavior
34
42
  * Customized state values
35
43
  * Parallel events
@@ -39,11 +47,11 @@ Class definition:
39
47
 
40
48
  ```ruby
41
49
  class Vehicle
42
- attr_accessor :seatbelt_on, :time_used, :auto_shop_busy
50
+ attr_accessor :seatbelt_on, :time_used, :auto_shop_busy, :parking_meter_number
43
51
 
44
52
  state_machine :state, initial: :parked do
45
53
  before_transition parked: any - :parked, do: :put_on_seatbelt
46
-
54
+
47
55
  after_transition on: :crash, do: :tow
48
56
  after_transition on: :repair, do: :fix
49
57
  after_transition any => :parked do |vehicle, transition|
@@ -62,6 +70,18 @@ class Vehicle
62
70
  transition [:idling, :first_gear] => :parked
63
71
  end
64
72
 
73
+ before_transition on: :park do |vehicle, transition|
74
+ # If using Rails:
75
+ # options = transition.args.extract_options!
76
+
77
+ options = transition.args.last.is_a?(Hash) ? transition.args.pop : {}
78
+ meter_number = options[:meter_number]
79
+
80
+ unless meter_number.nil?
81
+ vehicle.parking_meter_number = meter_number
82
+ end
83
+ end
84
+
65
85
  event :ignite do
66
86
  transition stalled: same, parked: :idling
67
87
  end
@@ -131,6 +151,7 @@ class Vehicle
131
151
  @seatbelt_on = false
132
152
  @time_used = 0
133
153
  @auto_shop_busy = true
154
+ @parking_meter_number = nil
134
155
  super() # NOTE: This *must* be called, otherwise states won't get initialized
135
156
  end
136
157
 
@@ -201,6 +222,11 @@ vehicle.park! # => StateMachines:InvalidTransition: Cannot tra
201
222
  vehicle.state?(:parked) # => false
202
223
  vehicle.state?(:invalid) # => IndexError: :invalid is an invalid name
203
224
 
225
+ # Transition callbacks can receive arguments
226
+ vehicle.park(meter_number: '12345') # => true
227
+ vehicle.parked? # => true
228
+ vehicle.parking_meter_number # => "12345"
229
+
204
230
  # Namespaced machines have uniquely-generated methods
205
231
  vehicle.alarm_state # => 1
206
232
  vehicle.alarm_state_name # => :active
@@ -221,6 +247,206 @@ vehicle.alarm_state_name # => :active
221
247
 
222
248
  vehicle.fire_events!(:ignite, :enable_alarm) # => StateMachines:InvalidParallelTransition: Cannot run events in parallel: ignite, enable_alarm
223
249
 
250
+ # Coordinated State Management
251
+
252
+ State machines can coordinate with each other using state guards, allowing transitions to depend on the state of other state machines within the same object. This enables complex system modeling where components are interdependent.
253
+
254
+ ## State Guard Options
255
+
256
+ ### Single State Guards
257
+
258
+ * `:if_state` - Transition only if another state machine is in a specific state.
259
+ * `:unless_state` - Transition only if another state machine is NOT in a specific state.
260
+
261
+ ```ruby
262
+ class TorpedoSystem
263
+ state_machine :bay_doors, initial: :closed do
264
+ event :open do
265
+ transition closed: :open
266
+ end
267
+
268
+ event :close do
269
+ transition open: :closed
270
+ end
271
+ end
272
+
273
+ state_machine :torpedo_status, initial: :loaded do
274
+ event :fire_torpedo do
275
+ # Can only fire torpedo if bay doors are open
276
+ transition loaded: :fired, if_state: { bay_doors: :open }
277
+ end
278
+
279
+ event :reload do
280
+ # Can only reload if bay doors are closed (for safety)
281
+ transition fired: :loaded, unless_state: { bay_doors: :open }
282
+ end
283
+ end
284
+ end
285
+
286
+ system = TorpedoSystem.new
287
+ system.fire_torpedo # => false (bay doors are closed)
288
+
289
+ system.open_bay_doors!
290
+ system.fire_torpedo # => true (bay doors are now open)
291
+ ```
292
+
293
+ ### Multiple State Guards
294
+
295
+ * `:if_all_states` - Transition only if ALL specified state machines are in their respective states.
296
+ * `:unless_all_states` - Transition only if NOT ALL specified state machines are in their respective states.
297
+ * `:if_any_state` - Transition only if ANY of the specified state machines are in their respective states.
298
+ * `:unless_any_state` - Transition only if NONE of the specified state machines are in their respective states.
299
+
300
+ ```ruby
301
+ class StarshipBridge
302
+ state_machine :shields, initial: :down
303
+ state_machine :weapons, initial: :offline
304
+ state_machine :warp_core, initial: :stable
305
+
306
+ state_machine :alert_status, initial: :green do
307
+ event :red_alert do
308
+ # Red alert if ANY critical system needs attention
309
+ transition green: :red, if_any_state: { warp_core: :critical, shields: :down }
310
+ end
311
+
312
+ event :battle_stations do
313
+ # Battle stations only if ALL combat systems are ready
314
+ transition green: :battle, if_all_states: { shields: :up, weapons: :armed }
315
+ end
316
+ end
317
+ end
318
+ ```
319
+
320
+ ## Error Handling
321
+
322
+ State guards provide comprehensive error checking:
323
+
324
+ ```ruby
325
+ # Referencing a non-existent state machine
326
+ event :invalid, if_state: { nonexistent_machine: :some_state }
327
+ # => ArgumentError: State machine 'nonexistent_machine' is not defined for StarshipBridge
328
+
329
+ # Referencing a non-existent state
330
+ event :another_invalid, if_state: { shields: :nonexistent_state }
331
+ # => ArgumentError: State 'nonexistent_state' is not defined in state machine 'shields'
332
+ ```
333
+
334
+ # Asynchronous State Machines
335
+
336
+ State machines can operate asynchronously for high-performance applications. This is ideal for I/O-bound tasks, such as in web servers or other concurrent environments, where you don't want a long-running state transition (like one involving a network call) to block the entire thread.
337
+
338
+ This feature is powered by the [async](https://github.com/socketry/async) gem and uses `concurrent-ruby` for enterprise-grade thread safety.
339
+
340
+ ## Platform Compatibility
341
+
342
+ **Supported Platforms:**
343
+ * MRI Ruby (CRuby) 3.2+
344
+ * Other Ruby engines with full Fiber scheduler support
345
+
346
+ **Unsupported Platforms:**
347
+ * JRuby - Falls back to synchronous mode with warnings
348
+ * TruffleRuby - Falls back to synchronous mode with warnings
349
+
350
+ ## Basic Async Usage
351
+
352
+ Enable async mode by adding `async: true` to your state machine declaration:
353
+
354
+ ```ruby
355
+ class AutonomousDrone < StarfleetShip
356
+ # Async-enabled state machine for autonomous operation
357
+ state_machine :status, async: true, initial: :docked do
358
+ event :launch do
359
+ transition docked: :flying
360
+ end
361
+
362
+ event :land do
363
+ transition flying: :docked
364
+ end
365
+ end
366
+
367
+ # Mixed configuration: some machines async, others sync
368
+ state_machine :teleporter_status, async: true, initial: :offline do
369
+ event :power_up do
370
+ transition offline: :charging
371
+ end
372
+
373
+ event :teleport do
374
+ transition ready: :teleporting
375
+ end
376
+ end
377
+
378
+ # Weapons remain synchronous for safety
379
+ state_machine :weapons, initial: :disarmed do
380
+ event :arm do
381
+ transition disarmed: :armed
382
+ end
383
+ end
384
+ end
385
+ ```
386
+
387
+ ## Async Event Methods
388
+
389
+ Async-enabled machines automatically generate async versions of event methods:
390
+
391
+ ```ruby
392
+ drone = AutonomousDrone.new
393
+
394
+ # Within an Async context
395
+ Async do
396
+ # Async event firing - returns Async::Task
397
+ task = drone.launch_async
398
+ result = task.wait # => true
399
+
400
+ # Bang methods for strict error handling
401
+ drone.power_up_async! # => Async::Task (raises on failure)
402
+
403
+ # Generic async event firing
404
+ drone.fire_event_async(:teleport) # => Async::Task
405
+ end
406
+
407
+ # Outside Async context - raises error
408
+ drone.launch_async # => RuntimeError: launch_async must be called within an Async context
409
+ ```
410
+
411
+ ## Thread Safety
412
+
413
+ Async state machines use enterprise-grade thread safety with `concurrent-ruby`:
414
+
415
+ ```ruby
416
+ # Concurrent operations are automatically thread-safe
417
+ threads = []
418
+ 10.times do
419
+ threads << Thread.new do
420
+ Async do
421
+ drone.launch_async.wait
422
+ drone.land_async.wait
423
+ end
424
+ end
425
+ end
426
+ threads.each(&:join)
427
+ ```
428
+
429
+ ## Performance Considerations
430
+
431
+ * **Thread Safety**: Uses `Concurrent::ReentrantReadWriteLock` for optimal read/write performance.
432
+ * **Memory**: Each async-enabled object gets its own mutex (lazy-loaded).
433
+ * **Marshalling**: Objects with async state machines can be serialized (mutex excluded/recreated).
434
+ * **Mixed Mode**: You can mix async and sync state machines in the same class.
435
+
436
+ ## Dependencies
437
+
438
+ Async functionality requires:
439
+
440
+ ```ruby
441
+ # Gemfile (automatically scoped to MRI Ruby)
442
+ platform :ruby do
443
+ gem 'async', '>= 2.25.0'
444
+ gem 'concurrent-ruby', '>= 1.3.5'
445
+ end
446
+ ```
447
+
448
+ *Note: These gems are only installed on supported platforms. JRuby/TruffleRuby won't attempt installation.*
449
+
224
450
  # Human-friendly names can be accessed for states/events
225
451
  Vehicle.human_state_name(:first_gear) # => "first gear"
226
452
  Vehicle.human_alarm_state_name(:active) # => "active"
@@ -237,6 +463,10 @@ vehicle.state_paths # => [[#<StateMachines
237
463
  vehicle.state_paths.to_states # => [:parked, :idling, :first_gear, :stalled, :second_gear, :third_gear]
238
464
  vehicle.state_paths.events # => [:park, :ignite, :shift_up, :idle, :crash, :repair, :shift_down]
239
465
 
466
+ # Possible states can be analyzed for a class
467
+ Vehicle.state_machine.states.to_a # [#<StateMachines::State name=:parked value="parked" initial=true>, #<StateMachines::State name=:idling value="idling" initial=false>, ...]
468
+ Vehicle.state_machines[:state].states.to_a # [#<StateMachines::State name=:parked value="parked" initial=true>, #<StateMachines::State name=:idling value="idling" initial=false>, ...]
469
+
240
470
  # Find all paths that start and end on certain states
241
471
  vehicle.state_paths(:from => :parked, :to => :first_gear) # => [[
242
472
  # #<StateMachines:Transition attribute=:state event=:ignite from="parked" ...>,
@@ -251,6 +481,182 @@ vehicle.state_name # => :parked
251
481
  # vehicle.state = :parked
252
482
  ```
253
483
 
484
+ ## Testing
485
+
486
+ State Machines provides an optional `TestHelper` module with assertion methods to make testing state machines easier and more expressive.
487
+
488
+ **Note: TestHelper is not required by default** - you must explicitly require it in your test files.
489
+
490
+ ### Setup
491
+
492
+ First, require the test helper module, then include it in your test class:
493
+
494
+ ```ruby
495
+ # For Minitest
496
+ require 'state_machines/test_helper'
497
+
498
+ class VehicleTest < Minitest::Test
499
+ include StateMachines::TestHelper
500
+
501
+ def test_initial_state
502
+ vehicle = Vehicle.new
503
+ assert_sm_state vehicle, :parked
504
+ end
505
+ end
506
+
507
+ # For RSpec
508
+ require 'state_machines/test_helper'
509
+
510
+ RSpec.describe Vehicle do
511
+ include StateMachines::TestHelper
512
+
513
+ it "starts in parked state" do
514
+ vehicle = Vehicle.new
515
+ assert_sm_state vehicle, :parked
516
+ end
517
+ end
518
+ ```
519
+
520
+ ### Available Assertions
521
+
522
+ The TestHelper provides both basic assertions and comprehensive state machine-specific assertions with `sm_` prefixes:
523
+
524
+ #### Basic Assertions
525
+
526
+ ```ruby
527
+ vehicle = Vehicle.new
528
+
529
+ # New standardized API (all methods prefixed with assert_sm_)
530
+ assert_sm_state(vehicle, :parked) # Uses default :state machine
531
+ assert_sm_state(vehicle, :parked, machine_name: :status) # Specify machine explicitly
532
+ assert_sm_can_transition(vehicle, :ignite) # Test transition capability
533
+ assert_sm_cannot_transition(vehicle, :shift_up) # Test transition restriction
534
+ assert_sm_transition(vehicle, :ignite, :idling) # Test actual transition
535
+
536
+ # Multi-FSM examples
537
+ assert_sm_state(vehicle, :inactive, machine_name: :insurance_state) # Test insurance state
538
+ assert_sm_can_transition(vehicle, :buy_insurance, machine_name: :insurance_state)
539
+ ```
540
+
541
+ #### Extended State Machine Assertions
542
+
543
+ ```ruby
544
+ machine = Vehicle.state_machine(:state)
545
+ vehicle = Vehicle.new
546
+
547
+ # State configuration
548
+ assert_sm_states_list machine, [:parked, :idling, :stalled]
549
+ assert_sm_initial_state machine, :parked
550
+
551
+ # Event behavior
552
+ assert_sm_event_triggers vehicle, :ignite
553
+ refute_sm_event_triggers vehicle, :shift_up
554
+ assert_sm_event_raises_error vehicle, :invalid_event, StateMachines::InvalidTransition
555
+
556
+ # Persistence (with ActiveRecord integration)
557
+ assert_sm_state_persisted record, expected: :active
558
+ ```
559
+
560
+ #### Indirect Event Testing
561
+
562
+ Test that methods trigger state machine events indirectly:
563
+
564
+ ```ruby
565
+ # Minitest style
566
+ vehicle = Vehicle.new
567
+ vehicle.ignite # Put in idling state
568
+
569
+ # Test that a custom method triggers a specific event
570
+ assert_sm_triggers_event(vehicle, :crash) do
571
+ vehicle.redline # Custom method that calls crash! internally
572
+ end
573
+
574
+ # Test multiple events
575
+ assert_sm_triggers_event(vehicle, [:crash, :emergency]) do
576
+ vehicle.emergency_stop
577
+ end
578
+
579
+ # Test on specific state machine (multi-FSM support)
580
+ assert_sm_triggers_event(vehicle, :disable, machine_name: :alarm) do
581
+ vehicle.turn_off_alarm
582
+ end
583
+ ```
584
+
585
+ ```ruby
586
+ # RSpec style (coming soon with proper matcher support)
587
+ RSpec.describe Vehicle do
588
+ include StateMachines::TestHelper
589
+
590
+ it "triggers crash when redlining" do
591
+ vehicle = Vehicle.new
592
+ vehicle.ignite
593
+
594
+ expect_to_trigger_event(vehicle, :crash) do
595
+ vehicle.redline
596
+ end
597
+ end
598
+ end
599
+ ```
600
+
601
+ #### Callback Definition Testing (TDD Support)
602
+
603
+ Verify that callbacks are properly defined in your state machine:
604
+
605
+ ```ruby
606
+ # Test after_transition callbacks
607
+ assert_after_transition(Vehicle, on: :crash, do: :tow)
608
+ assert_after_transition(Vehicle, from: :stalled, to: :parked, do: :log_repair)
609
+
610
+ # Test before_transition callbacks
611
+ assert_before_transition(Vehicle, from: :parked, do: :put_on_seatbelt)
612
+ assert_before_transition(Vehicle, on: :ignite, if: :seatbelt_on?)
613
+
614
+ # Works with machine instances too
615
+ machine = Vehicle.state_machine(:state)
616
+ assert_after_transition(machine, on: :crash, do: :tow)
617
+ ```
618
+
619
+ #### Multiple State Machine Support
620
+
621
+ The TestHelper fully supports objects with multiple state machines:
622
+
623
+ ```ruby
624
+ # Example: StarfleetShip with 3 state machines
625
+ ship = StarfleetShip.new
626
+
627
+ # Test states on different machines
628
+ assert_sm_state(ship, :docked, machine_name: :status) # Main ship status
629
+ assert_sm_state(ship, :down, machine_name: :shields) # Shield system
630
+ assert_sm_state(ship, :standby, machine_name: :weapons) # Weapons system
631
+
632
+ # Test transitions on specific machines
633
+ assert_sm_transition(ship, :undock, :impulse, machine_name: :status)
634
+ assert_sm_transition(ship, :raise_shields, :up, machine_name: :shields)
635
+ assert_sm_transition(ship, :arm_weapons, :armed, machine_name: :weapons)
636
+
637
+ # Test event triggering across multiple machines
638
+ assert_sm_triggers_event(ship, :red_alert, machine_name: :status) do
639
+ ship.engage_combat_mode # Custom method affecting multiple systems
640
+ end
641
+
642
+ assert_sm_triggers_event(ship, :raise_shields, machine_name: :shields) do
643
+ ship.engage_combat_mode
644
+ end
645
+
646
+ # Test callback definitions on specific machines
647
+ shields_machine = StarfleetShip.state_machine(:shields)
648
+ assert_before_transition(shields_machine, from: :down, to: :up, do: :power_up_shields)
649
+
650
+ # Test persistence across multiple machines
651
+ assert_sm_state_persisted(ship, "impulse", :status)
652
+ assert_sm_state_persisted(ship, "up", :shields)
653
+ assert_sm_state_persisted(ship, "armed", :weapons)
654
+ ```
655
+
656
+ The test helper works with both Minitest and RSpec, automatically detecting your testing framework.
657
+
658
+ **Note:** All methods use consistent keyword arguments with `machine_name:` as the last parameter, making the API intuitive and Grep-friendly.
659
+
254
660
  ## Additional Topics
255
661
 
256
662
  ### Explicit vs. Implicit Event Transitions
@@ -424,7 +830,7 @@ easily migrate from a different library, you can do so as shown below:
424
830
  ```ruby
425
831
  class Vehicle
426
832
  state_machine initial: :parked do
427
- ...
833
+ # ...
428
834
 
429
835
  state :parked do
430
836
  transition to: :idling, :on => [:ignite, :shift_up], if: :seatbelt_on?
@@ -460,12 +866,11 @@ example below:
460
866
  ```ruby
461
867
  class Vehicle
462
868
  state_machine initial: :parked do
463
- ...
869
+ # ...
464
870
 
465
871
  transition parked: :idling, :on => [:ignite, :shift_up]
466
872
  transition first_gear: :second_gear, second_gear: :third_gear, on: :shift_up
467
873
  transition [:idling, :first_gear] => :parked, on: :park
468
- transition [:idling, :first_gear] => :parked, on: :park
469
874
  transition all - [:parked, :stalled]: :stalled, unless: :auto_shop_busy?
470
875
  end
471
876
  end
@@ -493,12 +898,31 @@ class Vehicle
493
898
  transition [:idling, :first_gear] => :parked
494
899
  end
495
900
 
496
- ...
901
+ # ...
497
902
  end
498
903
  end
499
904
  ```
500
905
 
501
- However, there may be cases where the definition of a state machine is **dynamic**.
906
+ #### Draw state machines
907
+
908
+ State machines includes a default STDIORenderer for debugging state machines without external dependencies.
909
+ This renderer can be used to visualize the state machine in the console.
910
+
911
+ To use the renderer, simply call the `draw` method on the state machine:
912
+
913
+ ```ruby
914
+ Vehicle.state_machine.draw # Outputs the state machine diagram to the console
915
+ ```
916
+
917
+ You can customize the output by passing in options to the `draw` method, such as the output stream:
918
+
919
+ ```ruby
920
+ Vehicle.state_machine.draw(io: $stderr) # Outputs the state machine diagram to stderr
921
+ ```
922
+
923
+ #### Dynamic definitions
924
+
925
+ There may be cases where the definition of a state machine is **dynamic**.
502
926
  This means that you don't know the possible states or events for a machine until
503
927
  runtime. For example, you may allow users in your application to manage the
504
928
  state machine of a project or task in your system. This means that the list of
@@ -563,7 +987,7 @@ end
563
987
  vehicle = Vehicle.new # => #<Vehicle:0xb708412c @state="parked" ...>
564
988
  vehicle.state # => "parked"
565
989
  vehicle.machine.ignite # => true
566
- vehicle.machine.state # => "idling
990
+ vehicle.machine.state # => "idling"
567
991
  vehicle.state # => "idling"
568
992
  vehicle.machine.state_transitions # => [#<StateMachines:Transition ...>]
569
993
  vehicle.machine.definition.states.keys # => :first_gear, :second_gear, :parked, :idling
@@ -577,26 +1001,23 @@ transitions.
577
1001
 
578
1002
  Ruby versions officially supported and tested:
579
1003
 
580
- * Ruby (MRI) 2.0.0+
581
- * JRuby
582
- * Rubinius
1004
+ * Ruby (MRI) 3.0.0+
583
1005
 
584
1006
  For graphing state machine:
585
1007
 
586
- * [state_machines-graphviz](http://github.com/state-machines/state_machines-graphviz)
1008
+ * [state_machines-graphviz](https://github.com/state-machines/state_machines-graphviz)
587
1009
 
588
1010
  For documenting state machines:
589
1011
 
590
- * [state_machines-yard](http://github.com/state-machines/state_machines-yard)
591
-
1012
+ * [state_machines-yard](https://github.com/state-machines/state_machines-yard)
592
1013
 
593
- ## TODO
1014
+ For RSpec testing, use the custom RSpec matchers:
594
1015
 
595
- * Add matchers/assertions for rspec and minitest
1016
+ * [state_machines-rspec](https://github.com/state-machines/state_machines-rspec)
596
1017
 
597
1018
  ## Contributing
598
1019
 
599
- 1. Fork it ( https://github.com/state-machines/state_machines/fork )
1020
+ 1. Fork it ( <https://github.com/state-machines/state_machines/fork> )
600
1021
  2. Create your feature branch (`git checkout -b my-new-feature`)
601
1022
  3. Commit your changes (`git commit -am 'Add some feature'`)
602
1023
  4. Push to the branch (`git push origin my-new-feature`)
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StateMachines
4
+ module AsyncMode
5
+ # Extensions to Event class for async bang methods
6
+ module AsyncEventExtensions
7
+ # Generate async bang methods for events when async mode is enabled
8
+ def define_helper(scope, method, *args, &block)
9
+ result = super
10
+
11
+ # If this is an async-enabled machine and we're defining an event method
12
+ if scope == :instance && method !~ /_async[!]?$/ && machine.async_mode_enabled?
13
+ qualified_name = method.to_s
14
+
15
+ # Create async version that returns a task
16
+ machine.define_helper(scope, "#{qualified_name}_async") do |machine, object, *method_args, **kwargs|
17
+ # Find the machine that has this event
18
+ target_machine = object.class.state_machines.values.find { |m| m.events[name] }
19
+
20
+ unless defined?(::Async::Task) && ::Async::Task.current?
21
+ raise RuntimeError, "#{qualified_name}_async must be called within an Async context"
22
+ end
23
+
24
+ Async do
25
+ target_machine.events[name].fire(object, *method_args, **kwargs)
26
+ end
27
+ end
28
+
29
+ # Create async bang version that raises exceptions when awaited
30
+ machine.define_helper(scope, "#{qualified_name}_async!") do |machine, object, *method_args, **kwargs|
31
+ # Find the machine that has this event
32
+ target_machine = object.class.state_machines.values.find { |m| m.events[name] }
33
+
34
+ unless defined?(::Async::Task) && ::Async::Task.current?
35
+ raise RuntimeError, "#{qualified_name}_async! must be called within an Async context"
36
+ end
37
+
38
+ Async do
39
+ # Use fire method which will raise exceptions on invalid transitions
40
+ target_machine.events[name].fire(object, *method_args, **kwargs) || raise(StateMachines::InvalidTransition.new(object, target_machine, name))
41
+ end
42
+ end
43
+ end
44
+
45
+ result
46
+ end
47
+ end
48
+ end
49
+ end