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
@@ -0,0 +1,282 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module StateMachines
|
4
|
+
module AsyncMode
|
5
|
+
# Async-aware event firing capabilities using the async gem
|
6
|
+
module AsyncEvents
|
7
|
+
# Fires an event asynchronously using Async
|
8
|
+
# Returns an Async::Task that can be awaited for the result
|
9
|
+
#
|
10
|
+
# Example:
|
11
|
+
# Async do
|
12
|
+
# task = vehicle.async_fire_event(:ignite)
|
13
|
+
# result = task.wait # => true/false
|
14
|
+
# end
|
15
|
+
def async_fire_event(event_name, *args)
|
16
|
+
# Find the machine that has this event
|
17
|
+
machine = self.class.state_machines.values.find { |m| m.events[event_name] }
|
18
|
+
|
19
|
+
unless machine
|
20
|
+
raise ArgumentError, "Event #{event_name} not found in any state machine"
|
21
|
+
end
|
22
|
+
|
23
|
+
# Must be called within an Async context
|
24
|
+
unless defined?(::Async::Task) && ::Async::Task.current?
|
25
|
+
raise RuntimeError, "async_fire_event must be called within an Async context. Use: Async { vehicle.async_fire_event(:event) }"
|
26
|
+
end
|
27
|
+
|
28
|
+
Async do
|
29
|
+
machine.events[event_name].fire(self, *args)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Fires multiple events asynchronously across different state machines
|
34
|
+
# Returns an array of Async::Tasks for concurrent execution
|
35
|
+
#
|
36
|
+
# Example:
|
37
|
+
# Async do
|
38
|
+
# tasks = vehicle.async_fire_events(:ignite, :buy_insurance)
|
39
|
+
# results = tasks.map(&:wait) # => [true, true]
|
40
|
+
# end
|
41
|
+
def async_fire_events(*event_names)
|
42
|
+
event_names.map { |event_name| async_fire_event(event_name) }
|
43
|
+
end
|
44
|
+
|
45
|
+
# Fires an event asynchronously and waits for completion
|
46
|
+
# This is a convenience method that creates and waits for the task
|
47
|
+
#
|
48
|
+
# Example:
|
49
|
+
# result = vehicle.fire_event_async(:ignite) # => true/false
|
50
|
+
def fire_event_async(event_name, *args)
|
51
|
+
raise NoMethodError, "undefined method `fire_event_async' for #{self}" unless has_async_machines?
|
52
|
+
# Find the machine that has this event
|
53
|
+
machine = self.class.state_machines.values.find { |m| m.events[event_name] }
|
54
|
+
|
55
|
+
unless machine
|
56
|
+
raise ArgumentError, "Event #{event_name} not found in any state machine"
|
57
|
+
end
|
58
|
+
|
59
|
+
if defined?(::Async::Task) && ::Async::Task.current?
|
60
|
+
# Already in async context, just fire directly
|
61
|
+
machine.events[event_name].fire(self, *args)
|
62
|
+
else
|
63
|
+
# Create async context and wait for result
|
64
|
+
Async do
|
65
|
+
machine.events[event_name].fire(self, *args)
|
66
|
+
end.wait
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Fires multiple events asynchronously and waits for all completions
|
71
|
+
# Returns results in the same order as the input events
|
72
|
+
#
|
73
|
+
# Example:
|
74
|
+
# results = vehicle.fire_events_async(:ignite, :buy_insurance) # => [true, true]
|
75
|
+
def fire_events_async(*event_names)
|
76
|
+
raise NoMethodError, "undefined method `fire_events_async' for #{self}" unless has_async_machines?
|
77
|
+
if defined?(::Async::Task) && ::Async::Task.current?
|
78
|
+
# Already in async context, run concurrently
|
79
|
+
tasks = event_names.map { |event_name| async_fire_event(event_name) }
|
80
|
+
tasks.map(&:wait)
|
81
|
+
else
|
82
|
+
# Create async context and run concurrently
|
83
|
+
Async do
|
84
|
+
tasks = event_names.map { |event_name| async_fire_event(event_name) }
|
85
|
+
tasks.map(&:wait)
|
86
|
+
end.wait
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Fires an event asynchronously using Async and raises exception on failure
|
91
|
+
# Returns an Async::Task that raises StateMachines::InvalidTransition when awaited
|
92
|
+
#
|
93
|
+
# Example:
|
94
|
+
# Async do
|
95
|
+
# begin
|
96
|
+
# task = vehicle.async_fire_event!(:ignite)
|
97
|
+
# result = task.wait
|
98
|
+
# puts "Event fired successfully!"
|
99
|
+
# rescue StateMachines::InvalidTransition => e
|
100
|
+
# puts "Transition failed: #{e.message}"
|
101
|
+
# end
|
102
|
+
# end
|
103
|
+
def async_fire_event!(event_name, *args)
|
104
|
+
# Find the machine that has this event
|
105
|
+
machine = self.class.state_machines.values.find { |m| m.events[event_name] }
|
106
|
+
|
107
|
+
unless machine
|
108
|
+
raise ArgumentError, "Event #{event_name} not found in any state machine"
|
109
|
+
end
|
110
|
+
|
111
|
+
# Must be called within an Async context
|
112
|
+
unless defined?(::Async::Task) && ::Async::Task.current?
|
113
|
+
raise RuntimeError, "async_fire_event! must be called within an Async context. Use: Async { vehicle.async_fire_event!(:event) }"
|
114
|
+
end
|
115
|
+
|
116
|
+
Async do
|
117
|
+
# Use the bang version which raises exceptions on failure
|
118
|
+
machine.events[event_name].fire(self, *args) || raise(StateMachines::InvalidTransition.new(self, machine, event_name))
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# Fires an event asynchronously and waits for result, raising exceptions on failure
|
123
|
+
# This is a convenience method that creates and waits for the task
|
124
|
+
#
|
125
|
+
# Example:
|
126
|
+
# begin
|
127
|
+
# result = vehicle.fire_event_async!(:ignite)
|
128
|
+
# puts "Event fired successfully!"
|
129
|
+
# rescue StateMachines::InvalidTransition => e
|
130
|
+
# puts "Transition failed: #{e.message}"
|
131
|
+
# end
|
132
|
+
def fire_event_async!(event_name, *args)
|
133
|
+
raise NoMethodError, "undefined method `fire_event_async!' for #{self}" unless has_async_machines?
|
134
|
+
# Find the machine that has this event
|
135
|
+
machine = self.class.state_machines.values.find { |m| m.events[event_name] }
|
136
|
+
|
137
|
+
unless machine
|
138
|
+
raise ArgumentError, "Event #{event_name} not found in any state machine"
|
139
|
+
end
|
140
|
+
|
141
|
+
if defined?(::Async::Task) && ::Async::Task.current?
|
142
|
+
# Already in async context, just fire directly with bang behavior
|
143
|
+
machine.events[event_name].fire(self, *args) || raise(StateMachines::InvalidTransition.new(self, machine, event_name))
|
144
|
+
else
|
145
|
+
# Create async context and wait for result (may raise exception)
|
146
|
+
Async do
|
147
|
+
machine.events[event_name].fire(self, *args) || raise(StateMachines::InvalidTransition.new(self, machine, event_name))
|
148
|
+
end.wait
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# Dynamically handle individual event async methods
|
153
|
+
# This provides launch_async, launch_async!, arm_weapons_async, etc.
|
154
|
+
def method_missing(method_name, *args, **kwargs, &block)
|
155
|
+
method_str = method_name.to_s
|
156
|
+
|
157
|
+
# Check if this is an async event method
|
158
|
+
if method_str.end_with?('_async!')
|
159
|
+
# Remove the _async! suffix to get the base event method
|
160
|
+
base_method = method_str.chomp('_async!').to_sym
|
161
|
+
|
162
|
+
# Check if the base method exists and this machine is async-enabled
|
163
|
+
if respond_to?(base_method) && async_method_for_event?(base_method)
|
164
|
+
return handle_individual_event_async_bang(base_method, *args, **kwargs)
|
165
|
+
end
|
166
|
+
elsif method_str.end_with?('_async')
|
167
|
+
# Remove the _async suffix to get the base event method
|
168
|
+
base_method = method_str.chomp('_async').to_sym
|
169
|
+
|
170
|
+
# Check if the base method exists and this machine is async-enabled
|
171
|
+
if respond_to?(base_method) && async_method_for_event?(base_method)
|
172
|
+
return handle_individual_event_async(base_method, *args, **kwargs)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
# If not an async method, call the original method_missing
|
177
|
+
super
|
178
|
+
end
|
179
|
+
|
180
|
+
# Check if we should respond to async methods for this event
|
181
|
+
def respond_to_missing?(method_name, include_private = false)
|
182
|
+
# Only provide async methods if this object has async-enabled machines
|
183
|
+
return super unless has_async_machines?
|
184
|
+
|
185
|
+
method_str = method_name.to_s
|
186
|
+
|
187
|
+
if method_str.end_with?('_async!') || method_str.end_with?('_async')
|
188
|
+
base_method = method_str.chomp('_async!').chomp('_async').to_sym
|
189
|
+
return respond_to?(base_method) && async_method_for_event?(base_method)
|
190
|
+
end
|
191
|
+
|
192
|
+
super
|
193
|
+
end
|
194
|
+
|
195
|
+
# Check if this object has any async-enabled state machines
|
196
|
+
def has_async_machines?
|
197
|
+
self.class.state_machines.any? { |name, machine| machine.async_mode_enabled? }
|
198
|
+
end
|
199
|
+
|
200
|
+
private
|
201
|
+
|
202
|
+
# Check if this event method should have async versions
|
203
|
+
def async_method_for_event?(event_method)
|
204
|
+
# Find which machine contains this event
|
205
|
+
self.class.state_machines.each do |name, machine|
|
206
|
+
if machine.async_mode_enabled?
|
207
|
+
# Check if this event method belongs to this machine
|
208
|
+
machine.events.each do |event|
|
209
|
+
qualified_name = event.qualified_name
|
210
|
+
if qualified_name.to_sym == event_method || "#{qualified_name}!".to_sym == event_method
|
211
|
+
return true
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
false
|
217
|
+
end
|
218
|
+
|
219
|
+
# Handle individual event async methods (returns task)
|
220
|
+
def handle_individual_event_async(event_method, *args, **kwargs)
|
221
|
+
|
222
|
+
unless defined?(::Async::Task) && ::Async::Task.current?
|
223
|
+
raise RuntimeError, "#{event_method}_async must be called within an Async context"
|
224
|
+
end
|
225
|
+
|
226
|
+
Async do
|
227
|
+
send(event_method, *args, **kwargs)
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
# Handle individual event async bang methods (returns task, raises on failure)
|
232
|
+
def handle_individual_event_async_bang(event_method, *args, **kwargs)
|
233
|
+
# Extract event name from method and use bang version
|
234
|
+
bang_method = "#{event_method}!".to_sym
|
235
|
+
|
236
|
+
unless defined?(::Async::Task) && ::Async::Task.current?
|
237
|
+
raise RuntimeError, "#{event_method}_async! must be called within an Async context"
|
238
|
+
end
|
239
|
+
|
240
|
+
Async do
|
241
|
+
send(bang_method, *args, **kwargs)
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
# Extract event name from method name, handling namespaced events
|
246
|
+
def extract_event_name(method_name)
|
247
|
+
method_str = method_name.to_s
|
248
|
+
|
249
|
+
# Find the machine and event for this method
|
250
|
+
self.class.state_machines.each do |name, machine|
|
251
|
+
machine.events.each do |event|
|
252
|
+
qualified_name = event.qualified_name
|
253
|
+
if qualified_name.to_s == method_str || "#{qualified_name}!".to_s == method_str
|
254
|
+
return event.name
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
# Fallback: assume the method name is the event name
|
260
|
+
method_str.chomp('!').to_sym
|
261
|
+
end
|
262
|
+
|
263
|
+
public
|
264
|
+
|
265
|
+
# Fires multiple events concurrently within an async context
|
266
|
+
# This method should be called from within an Async block
|
267
|
+
#
|
268
|
+
# Example:
|
269
|
+
# Async do
|
270
|
+
# results = vehicle.fire_events_concurrent(:ignite, :buy_insurance)
|
271
|
+
# end
|
272
|
+
def fire_events_concurrent(*event_names)
|
273
|
+
unless defined?(::Async::Task) && ::Async::Task.current?
|
274
|
+
raise RuntimeError, "fire_events_concurrent must be called within an Async context"
|
275
|
+
end
|
276
|
+
|
277
|
+
tasks = async_fire_events(*event_names)
|
278
|
+
tasks.map(&:wait)
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|
282
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module StateMachines
|
4
|
+
module AsyncMode
|
5
|
+
# Enhanced machine class with async capabilities
|
6
|
+
module AsyncMachine
|
7
|
+
# Thread-safe state reading for machines
|
8
|
+
def read_safely(object, attribute, ivar = false)
|
9
|
+
if object.respond_to?(:read_state_safely)
|
10
|
+
object.read_state_safely(self, attribute, ivar)
|
11
|
+
else
|
12
|
+
read(object, attribute, ivar)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# Thread-safe state writing for machines
|
17
|
+
def write_safely(object, attribute, value, ivar = false)
|
18
|
+
if object.respond_to?(:write_state_safely)
|
19
|
+
object.write_state_safely(self, attribute, value, ivar)
|
20
|
+
else
|
21
|
+
write(object, attribute, value, ivar)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Fires an event asynchronously on the given object
|
26
|
+
# Returns an Async::Task for concurrent execution
|
27
|
+
def async_fire_event(object, event_name, *args)
|
28
|
+
unless defined?(::Async::Task) && ::Async::Task.current?
|
29
|
+
raise RuntimeError, "async_fire_event must be called within an Async context"
|
30
|
+
end
|
31
|
+
|
32
|
+
Async do
|
33
|
+
events[event_name].fire(object, *args)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Creates an async-aware transition collection
|
38
|
+
# Supports concurrent transition execution with proper synchronization
|
39
|
+
def create_async_transition_collection(transitions, options = {})
|
40
|
+
if defined?(AsyncTransitionCollection)
|
41
|
+
AsyncTransitionCollection.new(transitions, options)
|
42
|
+
else
|
43
|
+
# Fallback to regular collection if async collection isn't available
|
44
|
+
TransitionCollection.new(transitions, options)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Thread-safe callback execution for async operations
|
49
|
+
def run_callbacks_safely(type, object, context, transition)
|
50
|
+
if object.respond_to?(:state_machine_mutex)
|
51
|
+
object.state_machine_mutex.with_read_lock do
|
52
|
+
callbacks[type].each { |callback| callback.call(object, context, transition) }
|
53
|
+
end
|
54
|
+
else
|
55
|
+
callbacks[type].each { |callback| callback.call(object, context, transition) }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module StateMachines
|
4
|
+
module AsyncMode
|
5
|
+
# Error class for async-specific transition failures
|
6
|
+
class AsyncTransitionError < StateMachines::Error
|
7
|
+
def initialize(object, machines, failed_events)
|
8
|
+
@object = object
|
9
|
+
@machines = machines
|
10
|
+
@failed_events = failed_events
|
11
|
+
super("Failed to perform async transitions: #{failed_events.join(', ')}")
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :object, :machines, :failed_events
|
15
|
+
end
|
16
|
+
|
17
|
+
# Async-aware transition collection that can execute transitions concurrently
|
18
|
+
class AsyncTransitionCollection < TransitionCollection
|
19
|
+
# Performs transitions asynchronously using Async
|
20
|
+
# Provides better concurrency for I/O-bound operations
|
21
|
+
def perform_async(&block)
|
22
|
+
reset
|
23
|
+
|
24
|
+
unless defined?(::Async::Task) && ::Async::Task.current?
|
25
|
+
return Async do
|
26
|
+
perform_async(&block)
|
27
|
+
end.wait
|
28
|
+
end
|
29
|
+
|
30
|
+
if valid?
|
31
|
+
# Create async tasks for each transition
|
32
|
+
tasks = map do |transition|
|
33
|
+
Async do
|
34
|
+
if use_event_attributes? && !block_given?
|
35
|
+
transition.transient = true
|
36
|
+
transition.machine.write_safely(object, :event_transition, transition)
|
37
|
+
run_actions
|
38
|
+
transition
|
39
|
+
else
|
40
|
+
within_transaction do
|
41
|
+
catch(:halt) { run_callbacks(&block) }
|
42
|
+
rollback unless success?
|
43
|
+
end
|
44
|
+
transition
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Wait for all tasks to complete
|
50
|
+
completed_transitions = []
|
51
|
+
tasks.each do |task|
|
52
|
+
begin
|
53
|
+
result = task.wait
|
54
|
+
completed_transitions << result if result
|
55
|
+
rescue StandardError => e
|
56
|
+
# Handle individual transition failures
|
57
|
+
rollback
|
58
|
+
raise AsyncTransitionError.new(object, map(&:machine), [e.message])
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Check if all transitions succeeded
|
63
|
+
@success = completed_transitions.length == length
|
64
|
+
end
|
65
|
+
|
66
|
+
success?
|
67
|
+
end
|
68
|
+
|
69
|
+
# Performs transitions concurrently using threads
|
70
|
+
# Better for CPU-bound operations but requires more careful synchronization
|
71
|
+
def perform_threaded(&block)
|
72
|
+
reset
|
73
|
+
|
74
|
+
if valid?
|
75
|
+
# Use basic thread approach
|
76
|
+
threads = []
|
77
|
+
results = []
|
78
|
+
results_mutex = Concurrent::ReentrantReadWriteLock.new
|
79
|
+
|
80
|
+
each do |transition|
|
81
|
+
threads << Thread.new do
|
82
|
+
begin
|
83
|
+
result = if use_event_attributes? && !block_given?
|
84
|
+
transition.transient = true
|
85
|
+
transition.machine.write_safely(object, :event_transition, transition)
|
86
|
+
run_actions
|
87
|
+
transition
|
88
|
+
else
|
89
|
+
within_transaction do
|
90
|
+
catch(:halt) { run_callbacks(&block) }
|
91
|
+
rollback unless success?
|
92
|
+
end
|
93
|
+
transition
|
94
|
+
end
|
95
|
+
|
96
|
+
results_mutex.with_write_lock { results << result }
|
97
|
+
rescue StandardError => e
|
98
|
+
# Handle individual transition failures
|
99
|
+
rollback
|
100
|
+
raise AsyncTransitionError.new(object, [transition.machine], [e.message])
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Wait for all threads to complete
|
106
|
+
threads.each(&:join)
|
107
|
+
@success = results.length == length
|
108
|
+
end
|
109
|
+
|
110
|
+
success?
|
111
|
+
end
|
112
|
+
|
113
|
+
private
|
114
|
+
|
115
|
+
# Override run_actions to be thread-safe when needed
|
116
|
+
def run_actions(&block)
|
117
|
+
catch_exceptions do
|
118
|
+
@success = if block_given?
|
119
|
+
result = yield
|
120
|
+
actions.each { |action| results[action] = result }
|
121
|
+
!!result
|
122
|
+
else
|
123
|
+
actions.compact.each do |action|
|
124
|
+
next if skip_actions
|
125
|
+
|
126
|
+
# Use thread-safe write for results
|
127
|
+
if object.respond_to?(:state_machine_mutex)
|
128
|
+
object.state_machine_mutex.with_write_lock do
|
129
|
+
results[action] = object.send(action)
|
130
|
+
end
|
131
|
+
else
|
132
|
+
results[action] = object.send(action)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
results.values.all?
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module StateMachines
|
4
|
+
module AsyncMode
|
5
|
+
# Thread-safe state operations for async-enabled state machines
|
6
|
+
# Uses concurrent-ruby for enterprise-grade thread safety
|
7
|
+
module ThreadSafeState
|
8
|
+
# Gets or creates a reentrant mutex for thread-safe state operations on an object
|
9
|
+
# Each object gets its own mutex to avoid global locking
|
10
|
+
# Uses Concurrent::ReentrantReadWriteLock for better performance
|
11
|
+
def state_machine_mutex
|
12
|
+
@_state_machine_mutex ||= Concurrent::ReentrantReadWriteLock.new
|
13
|
+
end
|
14
|
+
|
15
|
+
# Thread-safe version of state reading
|
16
|
+
# Ensures atomic read operations across concurrent threads
|
17
|
+
def read_state_safely(machine, attribute, ivar = false)
|
18
|
+
state_machine_mutex.with_read_lock do
|
19
|
+
machine.read(self, attribute, ivar)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Thread-safe version of state writing
|
24
|
+
# Ensures atomic write operations across concurrent threads
|
25
|
+
def write_state_safely(machine, attribute, value, ivar = false)
|
26
|
+
state_machine_mutex.with_write_lock do
|
27
|
+
machine.write(self, attribute, value, ivar)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Handle marshalling by excluding the mutex (will be recreated when needed)
|
32
|
+
def marshal_dump
|
33
|
+
# Get instance variables excluding the mutex
|
34
|
+
vars = instance_variables.reject { |var| var == :@_state_machine_mutex }
|
35
|
+
vars.map { |var| [var, instance_variable_get(var)] }
|
36
|
+
end
|
37
|
+
|
38
|
+
# Restore marshalled object, mutex will be lazily recreated when needed
|
39
|
+
def marshal_load(data)
|
40
|
+
data.each do |var, value|
|
41
|
+
instance_variable_set(var, value)
|
42
|
+
end
|
43
|
+
# Don't set @_state_machine_mutex - let it be lazily created
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Ruby Engine Compatibility Check
|
4
|
+
# The async gem requires native extensions and Fiber scheduler support
|
5
|
+
# which are not available on JRuby or TruffleRuby
|
6
|
+
if RUBY_ENGINE == 'jruby' || RUBY_ENGINE == 'truffleruby'
|
7
|
+
raise LoadError, <<~ERROR
|
8
|
+
StateMachines::AsyncMode is not available on #{RUBY_ENGINE}.
|
9
|
+
|
10
|
+
The async gem requires native extensions (io-event) and Fiber scheduler support
|
11
|
+
which are not implemented in #{RUBY_ENGINE}. AsyncMode is only supported on:
|
12
|
+
|
13
|
+
• MRI Ruby (CRuby) 3.2+
|
14
|
+
• Other Ruby engines with full Fiber scheduler support
|
15
|
+
|
16
|
+
If you need async support on #{RUBY_ENGINE}, consider using:
|
17
|
+
• java.util.concurrent classes (JRuby)
|
18
|
+
• Native threading libraries for your platform
|
19
|
+
• Or stick with synchronous state machines
|
20
|
+
ERROR
|
21
|
+
end
|
22
|
+
|
23
|
+
# Load required gems with version constraints
|
24
|
+
gem 'async', '>= 2.25.0'
|
25
|
+
gem 'concurrent-ruby', '>= 1.3.5' # Security is not negotiable - enterprise-grade thread safety required
|
26
|
+
|
27
|
+
require 'async'
|
28
|
+
require 'concurrent-ruby'
|
29
|
+
|
30
|
+
# Load all async mode components
|
31
|
+
require_relative 'async_mode/thread_safe_state'
|
32
|
+
require_relative 'async_mode/async_events'
|
33
|
+
require_relative 'async_mode/async_event_extensions'
|
34
|
+
require_relative 'async_mode/async_machine'
|
35
|
+
require_relative 'async_mode/async_transition_collection'
|
36
|
+
|
37
|
+
module StateMachines
|
38
|
+
# AsyncMode provides asynchronous state machine capabilities using the async gem
|
39
|
+
# This module enables concurrent, thread-safe state operations for high-performance applications
|
40
|
+
#
|
41
|
+
# @example Basic usage
|
42
|
+
# class AutonomousDrone < StarfleetShip
|
43
|
+
# state_machine :teleporter_status, async: true do
|
44
|
+
# event :power_up do
|
45
|
+
# transition offline: :charging
|
46
|
+
# end
|
47
|
+
# end
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
# drone = AutonomousDrone.new
|
51
|
+
# Async do
|
52
|
+
# result = drone.fire_event_async(:power_up) # => true
|
53
|
+
# task = drone.power_up_async! # => Async::Task
|
54
|
+
# end
|
55
|
+
#
|
56
|
+
module AsyncMode
|
57
|
+
# All components are loaded from separate files:
|
58
|
+
# - ThreadSafeState: Mutex-based thread safety
|
59
|
+
# - AsyncEvents: Async event firing methods
|
60
|
+
# - AsyncEventExtensions: Event method generation
|
61
|
+
# - AsyncMachine: Machine-level async capabilities
|
62
|
+
# - AsyncTransitionCollection: Concurrent transition execution
|
63
|
+
end
|
64
|
+
end
|