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