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