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