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