state_machines 0.5.0 → 0.50.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/LICENSE.txt +1 -1
- data/README.md +443 -22
- data/lib/state_machines/async_mode/async_event_extensions.rb +49 -0
- data/lib/state_machines/async_mode/async_events.rb +282 -0
- data/lib/state_machines/async_mode/async_machine.rb +60 -0
- data/lib/state_machines/async_mode/async_transition_collection.rb +141 -0
- data/lib/state_machines/async_mode/thread_safe_state.rb +47 -0
- data/lib/state_machines/async_mode.rb +64 -0
- data/lib/state_machines/branch.rb +146 -86
- data/lib/state_machines/callback.rb +35 -32
- data/lib/state_machines/core.rb +3 -3
- data/lib/state_machines/core_ext/class/state_machine.rb +2 -0
- data/lib/state_machines/core_ext.rb +2 -0
- data/lib/state_machines/error.rb +7 -4
- data/lib/state_machines/eval_helpers.rb +197 -39
- data/lib/state_machines/event.rb +77 -58
- data/lib/state_machines/event_collection.rb +49 -39
- data/lib/state_machines/extensions.rb +6 -4
- data/lib/state_machines/helper_module.rb +4 -2
- data/lib/state_machines/integrations/base.rb +3 -1
- data/lib/state_machines/integrations.rb +19 -20
- data/lib/state_machines/machine/action_hooks.rb +53 -0
- data/lib/state_machines/machine/async_extensions.rb +88 -0
- data/lib/state_machines/machine/callbacks.rb +59 -0
- data/lib/state_machines/machine/class_methods.rb +97 -0
- data/lib/state_machines/machine/configuration.rb +134 -0
- data/lib/state_machines/machine/event_methods.rb +59 -0
- data/lib/state_machines/machine/helper_generators.rb +125 -0
- data/lib/state_machines/machine/integration.rb +70 -0
- data/lib/state_machines/machine/parsing.rb +77 -0
- data/lib/state_machines/machine/rendering.rb +17 -0
- data/lib/state_machines/machine/scoping.rb +44 -0
- data/lib/state_machines/machine/state_methods.rb +101 -0
- data/lib/state_machines/machine/utilities.rb +85 -0
- data/lib/state_machines/machine/validation.rb +39 -0
- data/lib/state_machines/machine.rb +425 -1011
- data/lib/state_machines/machine_collection.rb +28 -19
- data/lib/state_machines/macro_methods.rb +104 -102
- data/lib/state_machines/matcher.rb +31 -28
- data/lib/state_machines/matcher_helpers.rb +14 -12
- data/lib/state_machines/node_collection.rb +36 -29
- data/lib/state_machines/options_validator.rb +72 -0
- data/lib/state_machines/path.rb +60 -57
- data/lib/state_machines/path_collection.rb +39 -36
- data/lib/state_machines/state.rb +84 -47
- data/lib/state_machines/state_collection.rb +22 -19
- data/lib/state_machines/state_context.rb +40 -39
- data/lib/state_machines/stdio_renderer.rb +74 -0
- data/lib/state_machines/syntax_validator.rb +57 -0
- data/lib/state_machines/test_helper.rb +896 -0
- data/lib/state_machines/transition.rb +215 -199
- data/lib/state_machines/transition_collection.rb +187 -170
- data/lib/state_machines/version.rb +3 -1
- data/lib/state_machines.rb +4 -1
- metadata +39 -446
- data/.gitignore +0 -21
- data/.rspec +0 -3
- data/.ruby-gemset +0 -1
- data/.ruby-version +0 -1
- data/.travis.yml +0 -16
- data/Changelog.md +0 -22
- data/Contributors.md +0 -39
- data/Gemfile +0 -8
- data/Rakefile +0 -12
- data/Testing.md +0 -0
- data/lib/state_machines/assertions.rb +0 -40
- data/state_machines.gemspec +0 -22
- data/test/files/integrations/event_on_failure_integration.rb +0 -10
- data/test/files/integrations/vehicle.rb +0 -7
- data/test/files/models/auto_shop.rb +0 -31
- data/test/files/models/car.rb +0 -21
- data/test/files/models/driver.rb +0 -13
- data/test/files/models/model_base.rb +0 -6
- data/test/files/models/motorcycle.rb +0 -16
- data/test/files/models/traffic_light.rb +0 -47
- data/test/files/models/vehicle.rb +0 -127
- data/test/files/node.rb +0 -5
- data/test/files/switch.rb +0 -15
- data/test/functional/auto_shop_available_test.rb +0 -20
- data/test/functional/auto_shop_busy_test.rb +0 -25
- data/test/functional/car_backing_up_test.rb +0 -45
- data/test/functional/car_test.rb +0 -49
- data/test/functional/driver_default_nonstandard_test.rb +0 -13
- data/test/functional/motorcycle_test.rb +0 -52
- data/test/functional/traffic_light_caution_test.rb +0 -17
- data/test/functional/traffic_light_proceed_test.rb +0 -17
- data/test/functional/traffic_light_stop_test.rb +0 -26
- data/test/functional/vehicle_first_gear_test.rb +0 -42
- data/test/functional/vehicle_idling_test.rb +0 -59
- data/test/functional/vehicle_locked_test.rb +0 -29
- data/test/functional/vehicle_parked_test.rb +0 -53
- data/test/functional/vehicle_repaired_test.rb +0 -20
- data/test/functional/vehicle_second_gear_test.rb +0 -42
- data/test/functional/vehicle_stalled_test.rb +0 -65
- data/test/functional/vehicle_test.rb +0 -20
- data/test/functional/vehicle_third_gear_test.rb +0 -42
- data/test/functional/vehicle_unsaved_test.rb +0 -181
- data/test/functional/vehicle_with_event_attributes_test.rb +0 -30
- data/test/functional/vehicle_with_parallel_events_test.rb +0 -36
- data/test/test_helper.rb +0 -15
- data/test/unit/assertions/assert_exclusive_keys_test.rb +0 -22
- data/test/unit/assertions/assert_valid_key_test.rb +0 -12
- data/test/unit/branch/branch_test.rb +0 -28
- data/test/unit/branch/branch_with_conflicting_conditionals_test.rb +0 -27
- data/test/unit/branch/branch_with_conflicting_from_requirements_test.rb +0 -8
- data/test/unit/branch/branch_with_conflicting_on_requirements_test.rb +0 -8
- data/test/unit/branch/branch_with_conflicting_to_requirements_test.rb +0 -8
- data/test/unit/branch/branch_with_different_requirements_test.rb +0 -41
- data/test/unit/branch/branch_with_except_from_matcher_requirement_test.rb +0 -8
- data/test/unit/branch/branch_with_except_from_requirement_test.rb +0 -36
- data/test/unit/branch/branch_with_except_on_matcher_requirement_test.rb +0 -8
- data/test/unit/branch/branch_with_except_on_requirement_test.rb +0 -36
- data/test/unit/branch/branch_with_except_to_matcher_requirement_test.rb +0 -8
- data/test/unit/branch/branch_with_except_to_requirement_test.rb +0 -36
- data/test/unit/branch/branch_with_from_matcher_requirement_test.rb +0 -20
- data/test/unit/branch/branch_with_from_requirement_test.rb +0 -45
- data/test/unit/branch/branch_with_if_conditional_test.rb +0 -27
- data/test/unit/branch/branch_with_implicit_and_explicit_requirements_test.rb +0 -23
- data/test/unit/branch/branch_with_implicit_from_requirement_matcher_test.rb +0 -20
- data/test/unit/branch/branch_with_implicit_requirement_test.rb +0 -20
- data/test/unit/branch/branch_with_implicit_to_requirement_matcher_test.rb +0 -16
- data/test/unit/branch/branch_with_multiple_except_from_requirements_test.rb +0 -20
- data/test/unit/branch/branch_with_multiple_except_on_requirements_test.rb +0 -16
- data/test/unit/branch/branch_with_multiple_except_to_requirements_test.rb +0 -20
- data/test/unit/branch/branch_with_multiple_from_requirements_test.rb +0 -16
- data/test/unit/branch/branch_with_multiple_if_conditionals_test.rb +0 -20
- data/test/unit/branch/branch_with_multiple_implicit_requirements_test.rb +0 -53
- data/test/unit/branch/branch_with_multiple_to_requirements_test.rb +0 -20
- data/test/unit/branch/branch_with_multiple_unless_conditionals_test.rb +0 -20
- data/test/unit/branch/branch_with_nil_requirements_test.rb +0 -28
- data/test/unit/branch/branch_with_no_requirements_test.rb +0 -36
- data/test/unit/branch/branch_with_on_matcher_requirement_test.rb +0 -16
- data/test/unit/branch/branch_with_on_requirement_test.rb +0 -45
- data/test/unit/branch/branch_with_to_matcher_requirement_test.rb +0 -20
- data/test/unit/branch/branch_with_to_requirement_test.rb +0 -45
- data/test/unit/branch/branch_with_unless_conditional_test.rb +0 -27
- data/test/unit/branch/branch_without_guards_test.rb +0 -27
- data/test/unit/callback/callback_by_default_test.rb +0 -25
- data/test/unit/callback/callback_test.rb +0 -53
- data/test/unit/callback/callback_with_application_bound_object_test.rb +0 -23
- data/test/unit/callback/callback_with_application_terminator_test.rb +0 -24
- data/test/unit/callback/callback_with_arguments_test.rb +0 -14
- data/test/unit/callback/callback_with_around_type_and_arguments_test.rb +0 -25
- data/test/unit/callback/callback_with_around_type_and_block_test.rb +0 -44
- data/test/unit/callback/callback_with_around_type_and_bound_method_test.rb +0 -23
- data/test/unit/callback/callback_with_around_type_and_multiple_methods_test.rb +0 -93
- data/test/unit/callback/callback_with_around_type_and_terminator_test.rb +0 -17
- data/test/unit/callback/callback_with_block_test.rb +0 -20
- data/test/unit/callback/callback_with_bound_method_and_arguments_test.rb +0 -28
- data/test/unit/callback/callback_with_bound_method_test.rb +0 -35
- data/test/unit/callback/callback_with_do_method_test.rb +0 -18
- data/test/unit/callback/callback_with_explicit_requirements_test.rb +0 -32
- data/test/unit/callback/callback_with_if_condition_test.rb +0 -17
- data/test/unit/callback/callback_with_implicit_requirements_test.rb +0 -32
- data/test/unit/callback/callback_with_method_argument_test.rb +0 -18
- data/test/unit/callback/callback_with_mixed_methods_test.rb +0 -31
- data/test/unit/callback/callback_with_multiple_bound_methods_test.rb +0 -21
- data/test/unit/callback/callback_with_multiple_do_methods_test.rb +0 -29
- data/test/unit/callback/callback_with_multiple_method_arguments_test.rb +0 -29
- data/test/unit/callback/callback_with_terminator_test.rb +0 -22
- data/test/unit/callback/callback_with_unbound_method_test.rb +0 -14
- data/test/unit/callback/callback_with_unless_condition_test.rb +0 -17
- data/test/unit/callback/callback_without_arguments_test.rb +0 -14
- data/test/unit/callback/callback_without_terminator_test.rb +0 -12
- data/test/unit/error/error_by_default_test.rb +0 -21
- data/test/unit/error/error_with_message_test.rb +0 -23
- data/test/unit/eval_helper/eval_helpers_base_test.rb +0 -8
- data/test/unit/eval_helper/eval_helpers_proc_block_and_explicit_arguments_test.rb +0 -14
- data/test/unit/eval_helper/eval_helpers_proc_block_and_implicit_arguments_test.rb +0 -14
- data/test/unit/eval_helper/eval_helpers_proc_test.rb +0 -13
- data/test/unit/eval_helper/eval_helpers_proc_with_arguments_test.rb +0 -13
- data/test/unit/eval_helper/eval_helpers_proc_with_block_test.rb +0 -13
- data/test/unit/eval_helper/eval_helpers_proc_with_block_without_arguments_test.rb +0 -18
- data/test/unit/eval_helper/eval_helpers_proc_with_block_without_object_test.rb +0 -14
- data/test/unit/eval_helper/eval_helpers_proc_without_arguments_test.rb +0 -19
- data/test/unit/eval_helper/eval_helpers_string_test.rb +0 -25
- data/test/unit/eval_helper/eval_helpers_string_with_block_test.rb +0 -12
- data/test/unit/eval_helper/eval_helpers_symbol_method_missing_test.rb +0 -20
- data/test/unit/eval_helper/eval_helpers_symbol_private_test.rb +0 -17
- data/test/unit/eval_helper/eval_helpers_symbol_protected_test.rb +0 -17
- data/test/unit/eval_helper/eval_helpers_symbol_tainted_method_test.rb +0 -18
- data/test/unit/eval_helper/eval_helpers_symbol_test.rb +0 -16
- data/test/unit/eval_helper/eval_helpers_symbol_with_arguments_and_block_test.rb +0 -16
- data/test/unit/eval_helper/eval_helpers_symbol_with_arguments_test.rb +0 -16
- data/test/unit/eval_helper/eval_helpers_symbol_with_block_test.rb +0 -16
- data/test/unit/eval_helper/eval_helpers_test.rb +0 -13
- data/test/unit/event/event_after_being_copied_test.rb +0 -17
- data/test/unit/event/event_by_default_test.rb +0 -60
- data/test/unit/event/event_context_test.rb +0 -16
- data/test/unit/event/event_on_failure_test.rb +0 -44
- data/test/unit/event/event_test.rb +0 -34
- data/test/unit/event/event_transitions_test.rb +0 -62
- data/test/unit/event/event_with_conflicting_helpers_after_definition_test.rb +0 -79
- data/test/unit/event/event_with_conflicting_helpers_before_definition_test.rb +0 -58
- data/test/unit/event/event_with_conflicting_machine_test.rb +0 -48
- data/test/unit/event/event_with_dynamic_human_name_test.rb +0 -26
- data/test/unit/event/event_with_human_name_test.rb +0 -13
- data/test/unit/event/event_with_invalid_current_state_test.rb +0 -30
- data/test/unit/event/event_with_machine_action_test.rb +0 -33
- data/test/unit/event/event_with_marshalling_test.rb +0 -47
- data/test/unit/event/event_with_matching_disabled_transitions_test.rb +0 -115
- data/test/unit/event/event_with_matching_enabled_transitions_test.rb +0 -75
- data/test/unit/event/event_with_multiple_transitions_test.rb +0 -61
- data/test/unit/event/event_with_namespace_test.rb +0 -34
- data/test/unit/event/event_with_transition_with_blacklisted_to_state_test.rb +0 -60
- data/test/unit/event/event_with_transition_with_loopback_state_test.rb +0 -36
- data/test/unit/event/event_with_transition_with_nil_to_state_test.rb +0 -36
- data/test/unit/event/event_with_transition_with_whitelisted_to_state_test.rb +0 -51
- data/test/unit/event/event_with_transition_without_to_state_test.rb +0 -36
- data/test/unit/event/event_with_transitions_test.rb +0 -32
- data/test/unit/event/event_without_matching_transitions_test.rb +0 -41
- data/test/unit/event/event_without_transitions_test.rb +0 -28
- data/test/unit/event/invalid_event_test.rb +0 -20
- data/test/unit/event_collection/event_collection_attribute_with_machine_action_test.rb +0 -62
- data/test/unit/event_collection/event_collection_attribute_with_namespaced_machine_test.rb +0 -36
- data/test/unit/event_collection/event_collection_by_default_test.rb +0 -26
- data/test/unit/event_collection/event_collection_test.rb +0 -39
- data/test/unit/event_collection/event_collection_with_custom_machine_attribute_test.rb +0 -31
- data/test/unit/event_collection/event_collection_with_events_with_transitions_test.rb +0 -76
- data/test/unit/event_collection/event_collection_with_multiple_events_test.rb +0 -27
- data/test/unit/event_collection/event_collection_with_validations_test.rb +0 -74
- data/test/unit/event_collection/event_collection_without_machine_action_test.rb +0 -18
- data/test/unit/event_collection/event_string_collection_test.rb +0 -31
- data/test/unit/helper_module_test.rb +0 -17
- data/test/unit/integrations/integration_finder_test.rb +0 -16
- data/test/unit/integrations/integration_matcher_test.rb +0 -29
- data/test/unit/invalid_transition/invalid_parallel_transition_test.rb +0 -18
- data/test/unit/invalid_transition/invalid_transition_test.rb +0 -47
- data/test/unit/invalid_transition/invalid_transition_with_integration_test.rb +0 -45
- data/test/unit/invalid_transition/invalid_transition_with_namespace_test.rb +0 -32
- data/test/unit/machine/machine_after_being_copied_test.rb +0 -62
- data/test/unit/machine/machine_after_changing_initial_state.rb +0 -28
- data/test/unit/machine/machine_after_changing_owner_class_test.rb +0 -31
- data/test/unit/machine/machine_by_default_test.rb +0 -160
- data/test/unit/machine/machine_finder_custom_options_test.rb +0 -17
- data/test/unit/machine/machine_finder_with_existing_machine_on_superclass_test.rb +0 -85
- data/test/unit/machine/machine_finder_with_existing_on_same_class_test.rb +0 -23
- data/test/unit/machine/machine_finder_without_existing_machine_test.rb +0 -25
- data/test/unit/machine/machine_persistence_test.rb +0 -52
- data/test/unit/machine/machine_state_initialization_test.rb +0 -56
- data/test/unit/machine/machine_test.rb +0 -30
- data/test/unit/machine/machine_with_action_already_overridden_test.rb +0 -23
- data/test/unit/machine/machine_with_action_defined_in_class_test.rb +0 -37
- data/test/unit/machine/machine_with_action_defined_in_included_module_test.rb +0 -46
- data/test/unit/machine/machine_with_action_defined_in_superclass_test.rb +0 -43
- data/test/unit/machine/machine_with_action_undefined_test.rb +0 -33
- data/test/unit/machine/machine_with_cached_state_test.rb +0 -20
- data/test/unit/machine/machine_with_class_helpers_test.rb +0 -179
- data/test/unit/machine/machine_with_conflicting_helpers_after_definition_test.rb +0 -244
- data/test/unit/machine/machine_with_conflicting_helpers_before_definition_test.rb +0 -175
- data/test/unit/machine/machine_with_custom_action_test.rb +0 -11
- data/test/unit/machine/machine_with_custom_attribute_test.rb +0 -103
- data/test/unit/machine/machine_with_custom_initialize_test.rb +0 -24
- data/test/unit/machine/machine_with_custom_integration_test.rb +0 -72
- data/test/unit/machine/machine_with_custom_invalidation_test.rb +0 -39
- data/test/unit/machine/machine_with_custom_name_test.rb +0 -57
- data/test/unit/machine/machine_with_custom_plural_test.rb +0 -52
- data/test/unit/machine/machine_with_dynamic_initial_state_test.rb +0 -65
- data/test/unit/machine/machine_with_event_matchers_test.rb +0 -41
- data/test/unit/machine/machine_with_events_test.rb +0 -52
- data/test/unit/machine/machine_with_events_with_custom_human_names_test.rb +0 -18
- data/test/unit/machine/machine_with_events_with_transitions_test.rb +0 -37
- data/test/unit/machine/machine_with_existing_event_test.rb +0 -17
- data/test/unit/machine/machine_with_existing_machines_on_owner_class_test.rb +0 -20
- data/test/unit/machine/machine_with_existing_machines_with_same_attributes_on_owner_class_test.rb +0 -71
- data/test/unit/machine/machine_with_existing_machines_with_same_attributes_on_owner_subclass_test.rb +0 -31
- data/test/unit/machine/machine_with_existing_state_test.rb +0 -27
- data/test/unit/machine/machine_with_failure_callbacks_test.rb +0 -48
- data/test/unit/machine/machine_with_helpers_test.rb +0 -14
- data/test/unit/machine/machine_with_initial_state_with_value_and_owner_default.rb +0 -25
- data/test/unit/machine/machine_with_initialize_and_super_test.rb +0 -17
- data/test/unit/machine/machine_with_initialize_arguments_and_block_test.rb +0 -31
- data/test/unit/machine/machine_with_initialize_without_super_test.rb +0 -17
- data/test/unit/machine/machine_with_instance_helpers_test.rb +0 -179
- data/test/unit/machine/machine_with_integration_test.rb +0 -72
- data/test/unit/machine/machine_with_multiple_events_test.rb +0 -32
- data/test/unit/machine/machine_with_namespace_test.rb +0 -48
- data/test/unit/machine/machine_with_nil_action_test.rb +0 -27
- data/test/unit/machine/machine_with_other_states.rb +0 -22
- data/test/unit/machine/machine_with_owner_subclass_test.rb +0 -18
- data/test/unit/machine/machine_with_paths_test.rb +0 -25
- data/test/unit/machine/machine_with_private_action_test.rb +0 -43
- data/test/unit/machine/machine_with_state_matchers_test.rb +0 -41
- data/test/unit/machine/machine_with_state_with_matchers_test.rb +0 -19
- data/test/unit/machine/machine_with_states_test.rb +0 -55
- data/test/unit/machine/machine_with_states_with_behaviors_test.rb +0 -23
- data/test/unit/machine/machine_with_states_with_custom_human_names_test.rb +0 -18
- data/test/unit/machine/machine_with_states_with_custom_values_test.rb +0 -21
- data/test/unit/machine/machine_with_states_with_runtime_dependencies_test.rb +0 -19
- data/test/unit/machine/machine_with_static_initial_state_test.rb +0 -49
- data/test/unit/machine/machine_with_superclass_conflicting_helpers_after_definition_test.rb +0 -36
- data/test/unit/machine/machine_with_transition_callbacks_test.rb +0 -144
- data/test/unit/machine/machine_with_transitions_test.rb +0 -87
- data/test/unit/machine/machine_without_initialization_test.rb +0 -31
- data/test/unit/machine/machine_without_initialize_test.rb +0 -14
- data/test/unit/machine/machine_without_integration_test.rb +0 -31
- data/test/unit/machine_collection/machine_collection_by_default_test.rb +0 -11
- data/test/unit/machine_collection/machine_collection_fire_test.rb +0 -80
- data/test/unit/machine_collection/machine_collection_fire_with_transactions_test.rb +0 -54
- data/test/unit/machine_collection/machine_collection_fire_with_validations_test.rb +0 -76
- data/test/unit/machine_collection/machine_collection_state_initialization_test.rb +0 -111
- data/test/unit/machine_collection/machine_collection_transitions_with_blank_events_test.rb +0 -25
- data/test/unit/machine_collection/machine_collection_transitions_with_custom_options_test.rb +0 -20
- data/test/unit/machine_collection/machine_collection_transitions_with_different_actions_test.rb +0 -26
- data/test/unit/machine_collection/machine_collection_transitions_with_exisiting_transitions_test.rb +0 -25
- data/test/unit/machine_collection/machine_collection_transitions_with_invalid_events_test.rb +0 -25
- data/test/unit/machine_collection/machine_collection_transitions_with_same_actions_test.rb +0 -31
- data/test/unit/machine_collection/machine_collection_transitions_with_transition_test.rb +0 -26
- data/test/unit/machine_collection/machine_collection_transitions_without_events_test.rb +0 -25
- data/test/unit/machine_collection/machine_collection_transitions_without_transition_test.rb +0 -27
- data/test/unit/matcher/all_matcher_test.rb +0 -29
- data/test/unit/matcher/blacklist_matcher_test.rb +0 -30
- data/test/unit/matcher/loopback_matcher_test.rb +0 -27
- data/test/unit/matcher/matcher_by_default_test.rb +0 -15
- data/test/unit/matcher/matcher_with_multiple_values_test.rb +0 -15
- data/test/unit/matcher/matcher_with_value_test.rb +0 -15
- data/test/unit/matcher/whitelist_matcher_test.rb +0 -30
- data/test/unit/matcher_helpers/matcher_helpers_all_test.rb +0 -14
- data/test/unit/matcher_helpers/matcher_helpers_any_test.rb +0 -14
- data/test/unit/matcher_helpers/matcher_helpers_same_test.rb +0 -13
- data/test/unit/node_collection/node_collection_after_being_copied_test.rb +0 -46
- data/test/unit/node_collection/node_collection_after_update_test.rb +0 -36
- data/test/unit/node_collection/node_collection_by_default_test.rb +0 -22
- data/test/unit/node_collection/node_collection_test.rb +0 -23
- data/test/unit/node_collection/node_collection_with_indices_test.rb +0 -42
- data/test/unit/node_collection/node_collection_with_matcher_contexts_test.rb +0 -25
- data/test/unit/node_collection/node_collection_with_nodes_test.rb +0 -46
- data/test/unit/node_collection/node_collection_with_numeric_index_test.rb +0 -24
- data/test/unit/node_collection/node_collection_with_postdefined_contexts_test.rb +0 -22
- data/test/unit/node_collection/node_collection_with_predefined_contexts_test.rb +0 -23
- data/test/unit/node_collection/node_collection_with_string_index_test.rb +0 -20
- data/test/unit/node_collection/node_collection_with_symbol_index_test.rb +0 -20
- data/test/unit/node_collection/node_collection_without_indices_test.rb +0 -30
- data/test/unit/path/path_by_default_test.rb +0 -54
- data/test/unit/path/path_test.rb +0 -14
- data/test/unit/path/path_with_available_transitions_after_reaching_target_test.rb +0 -40
- data/test/unit/path/path_with_available_transitions_test.rb +0 -54
- data/test/unit/path/path_with_deep_target_reached_test.rb +0 -50
- data/test/unit/path/path_with_deep_target_test.rb +0 -40
- data/test/unit/path/path_with_duplicates_test.rb +0 -32
- data/test/unit/path/path_with_encountered_transitions_test.rb +0 -34
- data/test/unit/path/path_with_guarded_transitions_test.rb +0 -42
- data/test/unit/path/path_with_reached_target_test.rb +0 -35
- data/test/unit/path/path_with_transitions_test.rb +0 -54
- data/test/unit/path/path_with_unreached_target_test.rb +0 -31
- data/test/unit/path/path_without_transitions_test.rb +0 -24
- data/test/unit/path_collection/path_collection_by_default_test.rb +0 -46
- data/test/unit/path_collection/path_collection_test.rb +0 -24
- data/test/unit/path_collection/path_collection_with_deep_paths_test.rb +0 -43
- data/test/unit/path_collection/path_collection_with_duplicate_nodes_test.rb +0 -31
- data/test/unit/path_collection/path_collection_with_from_state_test.rb +0 -27
- data/test/unit/path_collection/path_collection_with_paths_test.rb +0 -47
- data/test/unit/path_collection/path_collection_with_to_state_test.rb +0 -29
- data/test/unit/path_collection/path_with_guarded_paths_test.rb +0 -25
- data/test/unit/state/state_after_being_copied_test.rb +0 -19
- data/test/unit/state/state_by_default_test.rb +0 -41
- data/test/unit/state/state_final_test.rb +0 -28
- data/test/unit/state/state_initial_test.rb +0 -13
- data/test/unit/state/state_not_final_test.rb +0 -32
- data/test/unit/state/state_not_initial_test.rb +0 -13
- data/test/unit/state/state_test.rb +0 -44
- data/test/unit/state/state_with_cached_lambda_value_test.rb +0 -29
- data/test/unit/state/state_with_conflicting_helpers_after_definition_test.rb +0 -38
- data/test/unit/state/state_with_conflicting_helpers_before_definition_test.rb +0 -29
- data/test/unit/state/state_with_conflicting_machine_name_test.rb +0 -20
- data/test/unit/state/state_with_conflicting_machine_test.rb +0 -37
- data/test/unit/state/state_with_context_test.rb +0 -60
- data/test/unit/state/state_with_dynamic_human_name_test.rb +0 -25
- data/test/unit/state/state_with_existing_context_method_test.rb +0 -24
- data/test/unit/state/state_with_human_name_test.rb +0 -13
- data/test/unit/state/state_with_integer_value_test.rb +0 -32
- data/test/unit/state/state_with_invalid_method_call_test.rb +0 -21
- data/test/unit/state/state_with_lambda_value_test.rb +0 -37
- data/test/unit/state/state_with_matcher_test.rb +0 -18
- data/test/unit/state/state_with_multiple_contexts_test.rb +0 -57
- data/test/unit/state/state_with_name_test.rb +0 -43
- data/test/unit/state/state_with_namespace_test.rb +0 -22
- data/test/unit/state/state_with_nil_value_test.rb +0 -35
- data/test/unit/state/state_with_redefined_context_method_test.rb +0 -45
- data/test/unit/state/state_with_symbolic_value_test.rb +0 -32
- data/test/unit/state/state_with_valid_inherited_method_call_for_current_state_test.rb +0 -40
- data/test/unit/state/state_with_valid_method_call_for_current_state_test.rb +0 -33
- data/test/unit/state/state_with_valid_method_call_for_different_state_test.rb +0 -41
- data/test/unit/state/state_without_cached_lambda_value_test.rb +0 -25
- data/test/unit/state/state_without_name_test.rb +0 -39
- data/test/unit/state_collection/state_collection_by_default_test.rb +0 -21
- data/test/unit/state_collection/state_collection_string_test.rb +0 -35
- data/test/unit/state_collection/state_collection_test.rb +0 -74
- data/test/unit/state_collection/state_collection_with_custom_state_values_test.rb +0 -29
- data/test/unit/state_collection/state_collection_with_event_transitions_test.rb +0 -39
- data/test/unit/state_collection/state_collection_with_initial_state_test.rb +0 -40
- data/test/unit/state_collection/state_collection_with_namespace_test.rb +0 -21
- data/test/unit/state_collection/state_collection_with_state_behaviors_test.rb +0 -40
- data/test/unit/state_collection/state_collection_with_state_matchers_test.rb +0 -29
- data/test/unit/state_collection/state_collection_with_transition_callbacks_test.rb +0 -40
- data/test/unit/state_context/state_context_proxy_test.rb +0 -26
- data/test/unit/state_context/state_context_proxy_with_if_and_unless_conditions_test.rb +0 -42
- data/test/unit/state_context/state_context_proxy_with_if_condition_test.rb +0 -64
- data/test/unit/state_context/state_context_proxy_with_multiple_if_conditions_test.rb +0 -32
- data/test/unit/state_context/state_context_proxy_with_multiple_unless_conditions_test.rb +0 -32
- data/test/unit/state_context/state_context_proxy_with_unless_condition_test.rb +0 -64
- data/test/unit/state_context/state_context_proxy_without_conditions_test.rb +0 -31
- data/test/unit/state_context/state_context_test.rb +0 -28
- data/test/unit/state_context/state_context_transition_test.rb +0 -104
- data/test/unit/state_context/state_context_with_matching_transition_test.rb +0 -27
- data/test/unit/state_machine/state_machine_by_default_test.rb +0 -12
- data/test/unit/state_machine/state_machine_test.rb +0 -20
- data/test/unit/transition/transition_after_being_performed_test.rb +0 -48
- data/test/unit/transition/transition_after_being_persisted_test.rb +0 -46
- data/test/unit/transition/transition_after_being_rolled_back_test.rb +0 -35
- data/test/unit/transition/transition_equality_test.rb +0 -52
- data/test/unit/transition/transition_loopback_test.rb +0 -18
- data/test/unit/transition/transition_test.rb +0 -96
- data/test/unit/transition/transition_transient_test.rb +0 -20
- data/test/unit/transition/transition_with_action_test.rb +0 -27
- data/test/unit/transition/transition_with_after_callbacks_skipped_test.rb +0 -127
- data/test/unit/transition/transition_with_after_callbacks_test.rb +0 -93
- data/test/unit/transition/transition_with_around_callbacks_test.rb +0 -141
- data/test/unit/transition/transition_with_before_callbacks_skipped_test.rb +0 -30
- data/test/unit/transition/transition_with_before_callbacks_test.rb +0 -104
- data/test/unit/transition/transition_with_custom_machine_attribute_test.rb +0 -28
- data/test/unit/transition/transition_with_different_states_test.rb +0 -18
- data/test/unit/transition/transition_with_dynamic_to_value_test.rb +0 -19
- data/test/unit/transition/transition_with_failure_callbacks_test.rb +0 -84
- data/test/unit/transition/transition_with_invalid_nodes_test.rb +0 -29
- data/test/unit/transition/transition_with_mixed_callbacks_test.rb +0 -105
- data/test/unit/transition/transition_with_multiple_after_callbacks_test.rb +0 -40
- data/test/unit/transition/transition_with_multiple_around_callbacks_test.rb +0 -114
- data/test/unit/transition/transition_with_multiple_before_callbacks_test.rb +0 -40
- data/test/unit/transition/transition_with_multiple_failure_callbacks_test.rb +0 -40
- data/test/unit/transition/transition_with_namespace_test.rb +0 -47
- data/test/unit/transition/transition_with_perform_arguments_test.rb +0 -35
- data/test/unit/transition/transition_with_transactions_test.rb +0 -42
- data/test/unit/transition/transition_without_callbacks_test.rb +0 -33
- data/test/unit/transition/transition_without_reading_state_test.rb +0 -22
- data/test/unit/transition/transition_without_running_action_test.rb +0 -47
- data/test/unit/transition_collection/attribute_transition_collection_by_default_test.rb +0 -23
- data/test/unit/transition_collection/attribute_transition_collection_marshalling_test.rb +0 -64
- data/test/unit/transition_collection/attribute_transition_collection_with_action_error_test.rb +0 -44
- data/test/unit/transition_collection/attribute_transition_collection_with_action_failed_test.rb +0 -44
- data/test/unit/transition_collection/attribute_transition_collection_with_after_callback_error_test.rb +0 -32
- data/test/unit/transition_collection/attribute_transition_collection_with_after_callback_halt_test.rb +0 -33
- data/test/unit/transition_collection/attribute_transition_collection_with_around_after_yield_callback_error_test.rb +0 -32
- data/test/unit/transition_collection/attribute_transition_collection_with_around_callback_after_yield_error_test.rb +0 -32
- data/test/unit/transition_collection/attribute_transition_collection_with_around_callback_after_yield_halt_test.rb +0 -33
- data/test/unit/transition_collection/attribute_transition_collection_with_around_callback_before_yield_halt_test.rb +0 -33
- data/test/unit/transition_collection/attribute_transition_collection_with_before_callback_error_test.rb +0 -32
- data/test/unit/transition_collection/attribute_transition_collection_with_before_callback_halt_test.rb +0 -33
- data/test/unit/transition_collection/attribute_transition_collection_with_callbacks_test.rb +0 -68
- data/test/unit/transition_collection/attribute_transition_collection_with_event_transitions_test.rb +0 -41
- data/test/unit/transition_collection/attribute_transition_collection_with_events_test.rb +0 -44
- data/test/unit/transition_collection/attribute_transition_collection_with_skipped_after_callbacks_test.rb +0 -42
- data/test/unit/transition_collection/transition_collection_by_default_test.rb +0 -23
- data/test/unit/transition_collection/transition_collection_empty_with_block_test.rb +0 -23
- data/test/unit/transition_collection/transition_collection_empty_without_block_test.rb +0 -12
- data/test/unit/transition_collection/transition_collection_invalid_test.rb +0 -21
- data/test/unit/transition_collection/transition_collection_partial_invalid_test.rb +0 -69
- data/test/unit/transition_collection/transition_collection_test.rb +0 -26
- data/test/unit/transition_collection/transition_collection_valid_test.rb +0 -57
- data/test/unit/transition_collection/transition_collection_with_action_error_test.rb +0 -66
- data/test/unit/transition_collection/transition_collection_with_action_failed_test.rb +0 -60
- data/test/unit/transition_collection/transition_collection_with_action_hook_and_block_test.rb +0 -17
- data/test/unit/transition_collection/transition_collection_with_action_hook_and_skipped_action_test.rb +0 -17
- data/test/unit/transition_collection/transition_collection_with_action_hook_and_skipped_after_callbacks_test.rb +0 -37
- data/test/unit/transition_collection/transition_collection_with_action_hook_base_test.rb +0 -34
- data/test/unit/transition_collection/transition_collection_with_action_hook_error_test.rb +0 -29
- data/test/unit/transition_collection/transition_collection_with_action_hook_invalid_test.rb +0 -17
- data/test/unit/transition_collection/transition_collection_with_action_hook_multiple_test.rb +0 -79
- data/test/unit/transition_collection/transition_collection_with_action_hook_test.rb +0 -45
- data/test/unit/transition_collection/transition_collection_with_action_hook_with_different_actions_test.rb +0 -48
- data/test/unit/transition_collection/transition_collection_with_action_hook_with_nil_action_test.rb +0 -42
- data/test/unit/transition_collection/transition_collection_with_after_callback_halt_test.rb +0 -47
- data/test/unit/transition_collection/transition_collection_with_before_callback_halt_test.rb +0 -51
- data/test/unit/transition_collection/transition_collection_with_block_test.rb +0 -46
- data/test/unit/transition_collection/transition_collection_with_callbacks_test.rb +0 -135
- data/test/unit/transition_collection/transition_collection_with_different_actions_test.rb +0 -189
- data/test/unit/transition_collection/transition_collection_with_duplicate_actions_test.rb +0 -48
- data/test/unit/transition_collection/transition_collection_with_empty_actions_test.rb +0 -41
- data/test/unit/transition_collection/transition_collection_with_mixed_actions_test.rb +0 -41
- data/test/unit/transition_collection/transition_collection_with_skipped_actions_and_block_test.rb +0 -34
- data/test/unit/transition_collection/transition_collection_with_skipped_actions_test.rb +0 -69
- data/test/unit/transition_collection/transition_collection_with_skipped_after_callbacks_and_around_callbacks_test.rb +0 -53
- data/test/unit/transition_collection/transition_collection_with_skipped_after_callbacks_test.rb +0 -34
- data/test/unit/transition_collection/transition_collection_with_transactions_test.rb +0 -65
- data/test/unit/transition_collection/transition_collection_without_transactions_test.rb +0 -29
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 247ae1ee6fa7beb6ac29a68dfd400ea41a3923b973d842a5adb2ef78960b3266
|
4
|
+
data.tar.gz: 7240a29d3d87740ade0d0197e9c4c0192176023f23e453d1ba659e8be905afef
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 26e8c0a197cdc254c4157a57e3bcfabfef87240280555ae623d993e30ccb68ba6433e911da171f60beb0e756af54aa60ff18395c9d49c6719a06b22bd895bdca
|
7
|
+
data.tar.gz: 9dfbdceba5b428f415dfeeba91995ab52640282b2b6c2fb92dea266c97677263331706e6b37f09a6280dc3f578da27dae1f5ade628a655342e46c565ddbe05db
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
|
2
|
-
|
1
|
+

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