state_machines 0.5.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 +7 -4
- 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 +1 -1
- data/lib/state_machines/helper_module.rb +1 -1
- data/lib/state_machines/integrations.rb +8 -8
- data/lib/state_machines/machine.rb +372 -369
- 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 +35 -35
- data/lib/state_machines/state.rb +7 -6
- data/lib/state_machines/state_collection.rb +20 -19
- 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 +4 -434
- data/.gitignore +0 -21
- data/.rspec +0 -3
- data/.ruby-gemset +0 -1
- data/.ruby-version +0 -1
- data/.travis.yml +0 -16
- data/Changelog.md +0 -22
- data/Contributors.md +0 -39
- data/Gemfile +0 -8
- data/Rakefile +0 -12
- data/Testing.md +0 -0
- data/state_machines.gemspec +0 -22
- data/test/files/integrations/event_on_failure_integration.rb +0 -10
- data/test/files/integrations/vehicle.rb +0 -7
- data/test/files/models/auto_shop.rb +0 -31
- data/test/files/models/car.rb +0 -21
- data/test/files/models/driver.rb +0 -13
- data/test/files/models/model_base.rb +0 -6
- data/test/files/models/motorcycle.rb +0 -16
- data/test/files/models/traffic_light.rb +0 -47
- data/test/files/models/vehicle.rb +0 -127
- data/test/files/node.rb +0 -5
- data/test/files/switch.rb +0 -15
- data/test/functional/auto_shop_available_test.rb +0 -20
- data/test/functional/auto_shop_busy_test.rb +0 -25
- data/test/functional/car_backing_up_test.rb +0 -45
- data/test/functional/car_test.rb +0 -49
- data/test/functional/driver_default_nonstandard_test.rb +0 -13
- data/test/functional/motorcycle_test.rb +0 -52
- data/test/functional/traffic_light_caution_test.rb +0 -17
- data/test/functional/traffic_light_proceed_test.rb +0 -17
- data/test/functional/traffic_light_stop_test.rb +0 -26
- data/test/functional/vehicle_first_gear_test.rb +0 -42
- data/test/functional/vehicle_idling_test.rb +0 -59
- data/test/functional/vehicle_locked_test.rb +0 -29
- data/test/functional/vehicle_parked_test.rb +0 -53
- data/test/functional/vehicle_repaired_test.rb +0 -20
- data/test/functional/vehicle_second_gear_test.rb +0 -42
- data/test/functional/vehicle_stalled_test.rb +0 -65
- data/test/functional/vehicle_test.rb +0 -20
- data/test/functional/vehicle_third_gear_test.rb +0 -42
- data/test/functional/vehicle_unsaved_test.rb +0 -181
- data/test/functional/vehicle_with_event_attributes_test.rb +0 -30
- data/test/functional/vehicle_with_parallel_events_test.rb +0 -36
- data/test/test_helper.rb +0 -15
- data/test/unit/assertions/assert_exclusive_keys_test.rb +0 -22
- data/test/unit/assertions/assert_valid_key_test.rb +0 -12
- data/test/unit/branch/branch_test.rb +0 -28
- data/test/unit/branch/branch_with_conflicting_conditionals_test.rb +0 -27
- data/test/unit/branch/branch_with_conflicting_from_requirements_test.rb +0 -8
- data/test/unit/branch/branch_with_conflicting_on_requirements_test.rb +0 -8
- data/test/unit/branch/branch_with_conflicting_to_requirements_test.rb +0 -8
- data/test/unit/branch/branch_with_different_requirements_test.rb +0 -41
- data/test/unit/branch/branch_with_except_from_matcher_requirement_test.rb +0 -8
- data/test/unit/branch/branch_with_except_from_requirement_test.rb +0 -36
- data/test/unit/branch/branch_with_except_on_matcher_requirement_test.rb +0 -8
- data/test/unit/branch/branch_with_except_on_requirement_test.rb +0 -36
- data/test/unit/branch/branch_with_except_to_matcher_requirement_test.rb +0 -8
- data/test/unit/branch/branch_with_except_to_requirement_test.rb +0 -36
- data/test/unit/branch/branch_with_from_matcher_requirement_test.rb +0 -20
- data/test/unit/branch/branch_with_from_requirement_test.rb +0 -45
- data/test/unit/branch/branch_with_if_conditional_test.rb +0 -27
- data/test/unit/branch/branch_with_implicit_and_explicit_requirements_test.rb +0 -23
- data/test/unit/branch/branch_with_implicit_from_requirement_matcher_test.rb +0 -20
- data/test/unit/branch/branch_with_implicit_requirement_test.rb +0 -20
- data/test/unit/branch/branch_with_implicit_to_requirement_matcher_test.rb +0 -16
- data/test/unit/branch/branch_with_multiple_except_from_requirements_test.rb +0 -20
- data/test/unit/branch/branch_with_multiple_except_on_requirements_test.rb +0 -16
- data/test/unit/branch/branch_with_multiple_except_to_requirements_test.rb +0 -20
- data/test/unit/branch/branch_with_multiple_from_requirements_test.rb +0 -16
- data/test/unit/branch/branch_with_multiple_if_conditionals_test.rb +0 -20
- data/test/unit/branch/branch_with_multiple_implicit_requirements_test.rb +0 -53
- data/test/unit/branch/branch_with_multiple_to_requirements_test.rb +0 -20
- data/test/unit/branch/branch_with_multiple_unless_conditionals_test.rb +0 -20
- data/test/unit/branch/branch_with_nil_requirements_test.rb +0 -28
- data/test/unit/branch/branch_with_no_requirements_test.rb +0 -36
- data/test/unit/branch/branch_with_on_matcher_requirement_test.rb +0 -16
- data/test/unit/branch/branch_with_on_requirement_test.rb +0 -45
- data/test/unit/branch/branch_with_to_matcher_requirement_test.rb +0 -20
- data/test/unit/branch/branch_with_to_requirement_test.rb +0 -45
- data/test/unit/branch/branch_with_unless_conditional_test.rb +0 -27
- data/test/unit/branch/branch_without_guards_test.rb +0 -27
- data/test/unit/callback/callback_by_default_test.rb +0 -25
- data/test/unit/callback/callback_test.rb +0 -53
- data/test/unit/callback/callback_with_application_bound_object_test.rb +0 -23
- data/test/unit/callback/callback_with_application_terminator_test.rb +0 -24
- data/test/unit/callback/callback_with_arguments_test.rb +0 -14
- data/test/unit/callback/callback_with_around_type_and_arguments_test.rb +0 -25
- data/test/unit/callback/callback_with_around_type_and_block_test.rb +0 -44
- data/test/unit/callback/callback_with_around_type_and_bound_method_test.rb +0 -23
- data/test/unit/callback/callback_with_around_type_and_multiple_methods_test.rb +0 -93
- data/test/unit/callback/callback_with_around_type_and_terminator_test.rb +0 -17
- data/test/unit/callback/callback_with_block_test.rb +0 -20
- data/test/unit/callback/callback_with_bound_method_and_arguments_test.rb +0 -28
- data/test/unit/callback/callback_with_bound_method_test.rb +0 -35
- data/test/unit/callback/callback_with_do_method_test.rb +0 -18
- data/test/unit/callback/callback_with_explicit_requirements_test.rb +0 -32
- data/test/unit/callback/callback_with_if_condition_test.rb +0 -17
- data/test/unit/callback/callback_with_implicit_requirements_test.rb +0 -32
- data/test/unit/callback/callback_with_method_argument_test.rb +0 -18
- data/test/unit/callback/callback_with_mixed_methods_test.rb +0 -31
- data/test/unit/callback/callback_with_multiple_bound_methods_test.rb +0 -21
- data/test/unit/callback/callback_with_multiple_do_methods_test.rb +0 -29
- data/test/unit/callback/callback_with_multiple_method_arguments_test.rb +0 -29
- data/test/unit/callback/callback_with_terminator_test.rb +0 -22
- data/test/unit/callback/callback_with_unbound_method_test.rb +0 -14
- data/test/unit/callback/callback_with_unless_condition_test.rb +0 -17
- data/test/unit/callback/callback_without_arguments_test.rb +0 -14
- data/test/unit/callback/callback_without_terminator_test.rb +0 -12
- data/test/unit/error/error_by_default_test.rb +0 -21
- data/test/unit/error/error_with_message_test.rb +0 -23
- data/test/unit/eval_helper/eval_helpers_base_test.rb +0 -8
- data/test/unit/eval_helper/eval_helpers_proc_block_and_explicit_arguments_test.rb +0 -14
- data/test/unit/eval_helper/eval_helpers_proc_block_and_implicit_arguments_test.rb +0 -14
- data/test/unit/eval_helper/eval_helpers_proc_test.rb +0 -13
- data/test/unit/eval_helper/eval_helpers_proc_with_arguments_test.rb +0 -13
- data/test/unit/eval_helper/eval_helpers_proc_with_block_test.rb +0 -13
- data/test/unit/eval_helper/eval_helpers_proc_with_block_without_arguments_test.rb +0 -18
- data/test/unit/eval_helper/eval_helpers_proc_with_block_without_object_test.rb +0 -14
- data/test/unit/eval_helper/eval_helpers_proc_without_arguments_test.rb +0 -19
- data/test/unit/eval_helper/eval_helpers_string_test.rb +0 -25
- data/test/unit/eval_helper/eval_helpers_string_with_block_test.rb +0 -12
- data/test/unit/eval_helper/eval_helpers_symbol_method_missing_test.rb +0 -20
- data/test/unit/eval_helper/eval_helpers_symbol_private_test.rb +0 -17
- data/test/unit/eval_helper/eval_helpers_symbol_protected_test.rb +0 -17
- data/test/unit/eval_helper/eval_helpers_symbol_tainted_method_test.rb +0 -18
- data/test/unit/eval_helper/eval_helpers_symbol_test.rb +0 -16
- data/test/unit/eval_helper/eval_helpers_symbol_with_arguments_and_block_test.rb +0 -16
- data/test/unit/eval_helper/eval_helpers_symbol_with_arguments_test.rb +0 -16
- data/test/unit/eval_helper/eval_helpers_symbol_with_block_test.rb +0 -16
- data/test/unit/eval_helper/eval_helpers_test.rb +0 -13
- data/test/unit/event/event_after_being_copied_test.rb +0 -17
- data/test/unit/event/event_by_default_test.rb +0 -60
- data/test/unit/event/event_context_test.rb +0 -16
- data/test/unit/event/event_on_failure_test.rb +0 -44
- data/test/unit/event/event_test.rb +0 -34
- data/test/unit/event/event_transitions_test.rb +0 -62
- data/test/unit/event/event_with_conflicting_helpers_after_definition_test.rb +0 -79
- data/test/unit/event/event_with_conflicting_helpers_before_definition_test.rb +0 -58
- data/test/unit/event/event_with_conflicting_machine_test.rb +0 -48
- data/test/unit/event/event_with_dynamic_human_name_test.rb +0 -26
- data/test/unit/event/event_with_human_name_test.rb +0 -13
- data/test/unit/event/event_with_invalid_current_state_test.rb +0 -30
- data/test/unit/event/event_with_machine_action_test.rb +0 -33
- data/test/unit/event/event_with_marshalling_test.rb +0 -47
- data/test/unit/event/event_with_matching_disabled_transitions_test.rb +0 -115
- data/test/unit/event/event_with_matching_enabled_transitions_test.rb +0 -75
- data/test/unit/event/event_with_multiple_transitions_test.rb +0 -61
- data/test/unit/event/event_with_namespace_test.rb +0 -34
- data/test/unit/event/event_with_transition_with_blacklisted_to_state_test.rb +0 -60
- data/test/unit/event/event_with_transition_with_loopback_state_test.rb +0 -36
- data/test/unit/event/event_with_transition_with_nil_to_state_test.rb +0 -36
- data/test/unit/event/event_with_transition_with_whitelisted_to_state_test.rb +0 -51
- data/test/unit/event/event_with_transition_without_to_state_test.rb +0 -36
- data/test/unit/event/event_with_transitions_test.rb +0 -32
- data/test/unit/event/event_without_matching_transitions_test.rb +0 -41
- data/test/unit/event/event_without_transitions_test.rb +0 -28
- data/test/unit/event/invalid_event_test.rb +0 -20
- data/test/unit/event_collection/event_collection_attribute_with_machine_action_test.rb +0 -62
- data/test/unit/event_collection/event_collection_attribute_with_namespaced_machine_test.rb +0 -36
- data/test/unit/event_collection/event_collection_by_default_test.rb +0 -26
- data/test/unit/event_collection/event_collection_test.rb +0 -39
- data/test/unit/event_collection/event_collection_with_custom_machine_attribute_test.rb +0 -31
- data/test/unit/event_collection/event_collection_with_events_with_transitions_test.rb +0 -76
- data/test/unit/event_collection/event_collection_with_multiple_events_test.rb +0 -27
- data/test/unit/event_collection/event_collection_with_validations_test.rb +0 -74
- data/test/unit/event_collection/event_collection_without_machine_action_test.rb +0 -18
- data/test/unit/event_collection/event_string_collection_test.rb +0 -31
- data/test/unit/helper_module_test.rb +0 -17
- data/test/unit/integrations/integration_finder_test.rb +0 -16
- data/test/unit/integrations/integration_matcher_test.rb +0 -29
- data/test/unit/invalid_transition/invalid_parallel_transition_test.rb +0 -18
- data/test/unit/invalid_transition/invalid_transition_test.rb +0 -47
- data/test/unit/invalid_transition/invalid_transition_with_integration_test.rb +0 -45
- data/test/unit/invalid_transition/invalid_transition_with_namespace_test.rb +0 -32
- data/test/unit/machine/machine_after_being_copied_test.rb +0 -62
- data/test/unit/machine/machine_after_changing_initial_state.rb +0 -28
- data/test/unit/machine/machine_after_changing_owner_class_test.rb +0 -31
- data/test/unit/machine/machine_by_default_test.rb +0 -160
- data/test/unit/machine/machine_finder_custom_options_test.rb +0 -17
- data/test/unit/machine/machine_finder_with_existing_machine_on_superclass_test.rb +0 -85
- data/test/unit/machine/machine_finder_with_existing_on_same_class_test.rb +0 -23
- data/test/unit/machine/machine_finder_without_existing_machine_test.rb +0 -25
- data/test/unit/machine/machine_persistence_test.rb +0 -52
- data/test/unit/machine/machine_state_initialization_test.rb +0 -56
- data/test/unit/machine/machine_test.rb +0 -30
- data/test/unit/machine/machine_with_action_already_overridden_test.rb +0 -23
- data/test/unit/machine/machine_with_action_defined_in_class_test.rb +0 -37
- data/test/unit/machine/machine_with_action_defined_in_included_module_test.rb +0 -46
- data/test/unit/machine/machine_with_action_defined_in_superclass_test.rb +0 -43
- data/test/unit/machine/machine_with_action_undefined_test.rb +0 -33
- data/test/unit/machine/machine_with_cached_state_test.rb +0 -20
- data/test/unit/machine/machine_with_class_helpers_test.rb +0 -179
- data/test/unit/machine/machine_with_conflicting_helpers_after_definition_test.rb +0 -244
- data/test/unit/machine/machine_with_conflicting_helpers_before_definition_test.rb +0 -175
- data/test/unit/machine/machine_with_custom_action_test.rb +0 -11
- data/test/unit/machine/machine_with_custom_attribute_test.rb +0 -103
- data/test/unit/machine/machine_with_custom_initialize_test.rb +0 -24
- data/test/unit/machine/machine_with_custom_integration_test.rb +0 -72
- data/test/unit/machine/machine_with_custom_invalidation_test.rb +0 -39
- data/test/unit/machine/machine_with_custom_name_test.rb +0 -57
- data/test/unit/machine/machine_with_custom_plural_test.rb +0 -52
- data/test/unit/machine/machine_with_dynamic_initial_state_test.rb +0 -65
- data/test/unit/machine/machine_with_event_matchers_test.rb +0 -41
- data/test/unit/machine/machine_with_events_test.rb +0 -52
- data/test/unit/machine/machine_with_events_with_custom_human_names_test.rb +0 -18
- data/test/unit/machine/machine_with_events_with_transitions_test.rb +0 -37
- data/test/unit/machine/machine_with_existing_event_test.rb +0 -17
- data/test/unit/machine/machine_with_existing_machines_on_owner_class_test.rb +0 -20
- data/test/unit/machine/machine_with_existing_machines_with_same_attributes_on_owner_class_test.rb +0 -71
- data/test/unit/machine/machine_with_existing_machines_with_same_attributes_on_owner_subclass_test.rb +0 -31
- data/test/unit/machine/machine_with_existing_state_test.rb +0 -27
- data/test/unit/machine/machine_with_failure_callbacks_test.rb +0 -48
- data/test/unit/machine/machine_with_helpers_test.rb +0 -14
- data/test/unit/machine/machine_with_initial_state_with_value_and_owner_default.rb +0 -25
- data/test/unit/machine/machine_with_initialize_and_super_test.rb +0 -17
- data/test/unit/machine/machine_with_initialize_arguments_and_block_test.rb +0 -31
- data/test/unit/machine/machine_with_initialize_without_super_test.rb +0 -17
- data/test/unit/machine/machine_with_instance_helpers_test.rb +0 -179
- data/test/unit/machine/machine_with_integration_test.rb +0 -72
- data/test/unit/machine/machine_with_multiple_events_test.rb +0 -32
- data/test/unit/machine/machine_with_namespace_test.rb +0 -48
- data/test/unit/machine/machine_with_nil_action_test.rb +0 -27
- data/test/unit/machine/machine_with_other_states.rb +0 -22
- data/test/unit/machine/machine_with_owner_subclass_test.rb +0 -18
- data/test/unit/machine/machine_with_paths_test.rb +0 -25
- data/test/unit/machine/machine_with_private_action_test.rb +0 -43
- data/test/unit/machine/machine_with_state_matchers_test.rb +0 -41
- data/test/unit/machine/machine_with_state_with_matchers_test.rb +0 -19
- data/test/unit/machine/machine_with_states_test.rb +0 -55
- data/test/unit/machine/machine_with_states_with_behaviors_test.rb +0 -23
- data/test/unit/machine/machine_with_states_with_custom_human_names_test.rb +0 -18
- data/test/unit/machine/machine_with_states_with_custom_values_test.rb +0 -21
- data/test/unit/machine/machine_with_states_with_runtime_dependencies_test.rb +0 -19
- data/test/unit/machine/machine_with_static_initial_state_test.rb +0 -49
- data/test/unit/machine/machine_with_superclass_conflicting_helpers_after_definition_test.rb +0 -36
- data/test/unit/machine/machine_with_transition_callbacks_test.rb +0 -144
- data/test/unit/machine/machine_with_transitions_test.rb +0 -87
- data/test/unit/machine/machine_without_initialization_test.rb +0 -31
- data/test/unit/machine/machine_without_initialize_test.rb +0 -14
- data/test/unit/machine/machine_without_integration_test.rb +0 -31
- data/test/unit/machine_collection/machine_collection_by_default_test.rb +0 -11
- data/test/unit/machine_collection/machine_collection_fire_test.rb +0 -80
- data/test/unit/machine_collection/machine_collection_fire_with_transactions_test.rb +0 -54
- data/test/unit/machine_collection/machine_collection_fire_with_validations_test.rb +0 -76
- data/test/unit/machine_collection/machine_collection_state_initialization_test.rb +0 -111
- data/test/unit/machine_collection/machine_collection_transitions_with_blank_events_test.rb +0 -25
- data/test/unit/machine_collection/machine_collection_transitions_with_custom_options_test.rb +0 -20
- data/test/unit/machine_collection/machine_collection_transitions_with_different_actions_test.rb +0 -26
- data/test/unit/machine_collection/machine_collection_transitions_with_exisiting_transitions_test.rb +0 -25
- data/test/unit/machine_collection/machine_collection_transitions_with_invalid_events_test.rb +0 -25
- data/test/unit/machine_collection/machine_collection_transitions_with_same_actions_test.rb +0 -31
- data/test/unit/machine_collection/machine_collection_transitions_with_transition_test.rb +0 -26
- data/test/unit/machine_collection/machine_collection_transitions_without_events_test.rb +0 -25
- data/test/unit/machine_collection/machine_collection_transitions_without_transition_test.rb +0 -27
- data/test/unit/matcher/all_matcher_test.rb +0 -29
- data/test/unit/matcher/blacklist_matcher_test.rb +0 -30
- data/test/unit/matcher/loopback_matcher_test.rb +0 -27
- data/test/unit/matcher/matcher_by_default_test.rb +0 -15
- data/test/unit/matcher/matcher_with_multiple_values_test.rb +0 -15
- data/test/unit/matcher/matcher_with_value_test.rb +0 -15
- data/test/unit/matcher/whitelist_matcher_test.rb +0 -30
- data/test/unit/matcher_helpers/matcher_helpers_all_test.rb +0 -14
- data/test/unit/matcher_helpers/matcher_helpers_any_test.rb +0 -14
- data/test/unit/matcher_helpers/matcher_helpers_same_test.rb +0 -13
- data/test/unit/node_collection/node_collection_after_being_copied_test.rb +0 -46
- data/test/unit/node_collection/node_collection_after_update_test.rb +0 -36
- data/test/unit/node_collection/node_collection_by_default_test.rb +0 -22
- data/test/unit/node_collection/node_collection_test.rb +0 -23
- data/test/unit/node_collection/node_collection_with_indices_test.rb +0 -42
- data/test/unit/node_collection/node_collection_with_matcher_contexts_test.rb +0 -25
- data/test/unit/node_collection/node_collection_with_nodes_test.rb +0 -46
- data/test/unit/node_collection/node_collection_with_numeric_index_test.rb +0 -24
- data/test/unit/node_collection/node_collection_with_postdefined_contexts_test.rb +0 -22
- data/test/unit/node_collection/node_collection_with_predefined_contexts_test.rb +0 -23
- data/test/unit/node_collection/node_collection_with_string_index_test.rb +0 -20
- data/test/unit/node_collection/node_collection_with_symbol_index_test.rb +0 -20
- data/test/unit/node_collection/node_collection_without_indices_test.rb +0 -30
- data/test/unit/path/path_by_default_test.rb +0 -54
- data/test/unit/path/path_test.rb +0 -14
- data/test/unit/path/path_with_available_transitions_after_reaching_target_test.rb +0 -40
- data/test/unit/path/path_with_available_transitions_test.rb +0 -54
- data/test/unit/path/path_with_deep_target_reached_test.rb +0 -50
- data/test/unit/path/path_with_deep_target_test.rb +0 -40
- data/test/unit/path/path_with_duplicates_test.rb +0 -32
- data/test/unit/path/path_with_encountered_transitions_test.rb +0 -34
- data/test/unit/path/path_with_guarded_transitions_test.rb +0 -42
- data/test/unit/path/path_with_reached_target_test.rb +0 -35
- data/test/unit/path/path_with_transitions_test.rb +0 -54
- data/test/unit/path/path_with_unreached_target_test.rb +0 -31
- data/test/unit/path/path_without_transitions_test.rb +0 -24
- data/test/unit/path_collection/path_collection_by_default_test.rb +0 -46
- data/test/unit/path_collection/path_collection_test.rb +0 -24
- data/test/unit/path_collection/path_collection_with_deep_paths_test.rb +0 -43
- data/test/unit/path_collection/path_collection_with_duplicate_nodes_test.rb +0 -31
- data/test/unit/path_collection/path_collection_with_from_state_test.rb +0 -27
- data/test/unit/path_collection/path_collection_with_paths_test.rb +0 -47
- data/test/unit/path_collection/path_collection_with_to_state_test.rb +0 -29
- data/test/unit/path_collection/path_with_guarded_paths_test.rb +0 -25
- data/test/unit/state/state_after_being_copied_test.rb +0 -19
- data/test/unit/state/state_by_default_test.rb +0 -41
- data/test/unit/state/state_final_test.rb +0 -28
- data/test/unit/state/state_initial_test.rb +0 -13
- data/test/unit/state/state_not_final_test.rb +0 -32
- data/test/unit/state/state_not_initial_test.rb +0 -13
- data/test/unit/state/state_test.rb +0 -44
- data/test/unit/state/state_with_cached_lambda_value_test.rb +0 -29
- data/test/unit/state/state_with_conflicting_helpers_after_definition_test.rb +0 -38
- data/test/unit/state/state_with_conflicting_helpers_before_definition_test.rb +0 -29
- data/test/unit/state/state_with_conflicting_machine_name_test.rb +0 -20
- data/test/unit/state/state_with_conflicting_machine_test.rb +0 -37
- data/test/unit/state/state_with_context_test.rb +0 -60
- data/test/unit/state/state_with_dynamic_human_name_test.rb +0 -25
- data/test/unit/state/state_with_existing_context_method_test.rb +0 -24
- data/test/unit/state/state_with_human_name_test.rb +0 -13
- data/test/unit/state/state_with_integer_value_test.rb +0 -32
- data/test/unit/state/state_with_invalid_method_call_test.rb +0 -21
- data/test/unit/state/state_with_lambda_value_test.rb +0 -37
- data/test/unit/state/state_with_matcher_test.rb +0 -18
- data/test/unit/state/state_with_multiple_contexts_test.rb +0 -57
- data/test/unit/state/state_with_name_test.rb +0 -43
- data/test/unit/state/state_with_namespace_test.rb +0 -22
- data/test/unit/state/state_with_nil_value_test.rb +0 -35
- data/test/unit/state/state_with_redefined_context_method_test.rb +0 -45
- data/test/unit/state/state_with_symbolic_value_test.rb +0 -32
- data/test/unit/state/state_with_valid_inherited_method_call_for_current_state_test.rb +0 -40
- data/test/unit/state/state_with_valid_method_call_for_current_state_test.rb +0 -33
- data/test/unit/state/state_with_valid_method_call_for_different_state_test.rb +0 -41
- data/test/unit/state/state_without_cached_lambda_value_test.rb +0 -25
- data/test/unit/state/state_without_name_test.rb +0 -39
- data/test/unit/state_collection/state_collection_by_default_test.rb +0 -21
- data/test/unit/state_collection/state_collection_string_test.rb +0 -35
- data/test/unit/state_collection/state_collection_test.rb +0 -74
- data/test/unit/state_collection/state_collection_with_custom_state_values_test.rb +0 -29
- data/test/unit/state_collection/state_collection_with_event_transitions_test.rb +0 -39
- data/test/unit/state_collection/state_collection_with_initial_state_test.rb +0 -40
- data/test/unit/state_collection/state_collection_with_namespace_test.rb +0 -21
- data/test/unit/state_collection/state_collection_with_state_behaviors_test.rb +0 -40
- data/test/unit/state_collection/state_collection_with_state_matchers_test.rb +0 -29
- data/test/unit/state_collection/state_collection_with_transition_callbacks_test.rb +0 -40
- data/test/unit/state_context/state_context_proxy_test.rb +0 -26
- data/test/unit/state_context/state_context_proxy_with_if_and_unless_conditions_test.rb +0 -42
- data/test/unit/state_context/state_context_proxy_with_if_condition_test.rb +0 -64
- data/test/unit/state_context/state_context_proxy_with_multiple_if_conditions_test.rb +0 -32
- data/test/unit/state_context/state_context_proxy_with_multiple_unless_conditions_test.rb +0 -32
- data/test/unit/state_context/state_context_proxy_with_unless_condition_test.rb +0 -64
- data/test/unit/state_context/state_context_proxy_without_conditions_test.rb +0 -31
- data/test/unit/state_context/state_context_test.rb +0 -28
- data/test/unit/state_context/state_context_transition_test.rb +0 -104
- data/test/unit/state_context/state_context_with_matching_transition_test.rb +0 -27
- data/test/unit/state_machine/state_machine_by_default_test.rb +0 -12
- data/test/unit/state_machine/state_machine_test.rb +0 -20
- data/test/unit/transition/transition_after_being_performed_test.rb +0 -48
- data/test/unit/transition/transition_after_being_persisted_test.rb +0 -46
- data/test/unit/transition/transition_after_being_rolled_back_test.rb +0 -35
- data/test/unit/transition/transition_equality_test.rb +0 -52
- data/test/unit/transition/transition_loopback_test.rb +0 -18
- data/test/unit/transition/transition_test.rb +0 -96
- data/test/unit/transition/transition_transient_test.rb +0 -20
- data/test/unit/transition/transition_with_action_test.rb +0 -27
- data/test/unit/transition/transition_with_after_callbacks_skipped_test.rb +0 -127
- data/test/unit/transition/transition_with_after_callbacks_test.rb +0 -93
- data/test/unit/transition/transition_with_around_callbacks_test.rb +0 -141
- data/test/unit/transition/transition_with_before_callbacks_skipped_test.rb +0 -30
- data/test/unit/transition/transition_with_before_callbacks_test.rb +0 -104
- data/test/unit/transition/transition_with_custom_machine_attribute_test.rb +0 -28
- data/test/unit/transition/transition_with_different_states_test.rb +0 -18
- data/test/unit/transition/transition_with_dynamic_to_value_test.rb +0 -19
- data/test/unit/transition/transition_with_failure_callbacks_test.rb +0 -84
- data/test/unit/transition/transition_with_invalid_nodes_test.rb +0 -29
- data/test/unit/transition/transition_with_mixed_callbacks_test.rb +0 -105
- data/test/unit/transition/transition_with_multiple_after_callbacks_test.rb +0 -40
- data/test/unit/transition/transition_with_multiple_around_callbacks_test.rb +0 -114
- data/test/unit/transition/transition_with_multiple_before_callbacks_test.rb +0 -40
- data/test/unit/transition/transition_with_multiple_failure_callbacks_test.rb +0 -40
- data/test/unit/transition/transition_with_namespace_test.rb +0 -47
- data/test/unit/transition/transition_with_perform_arguments_test.rb +0 -35
- data/test/unit/transition/transition_with_transactions_test.rb +0 -42
- data/test/unit/transition/transition_without_callbacks_test.rb +0 -33
- data/test/unit/transition/transition_without_reading_state_test.rb +0 -22
- data/test/unit/transition/transition_without_running_action_test.rb +0 -47
- data/test/unit/transition_collection/attribute_transition_collection_by_default_test.rb +0 -23
- data/test/unit/transition_collection/attribute_transition_collection_marshalling_test.rb +0 -64
- data/test/unit/transition_collection/attribute_transition_collection_with_action_error_test.rb +0 -44
- data/test/unit/transition_collection/attribute_transition_collection_with_action_failed_test.rb +0 -44
- data/test/unit/transition_collection/attribute_transition_collection_with_after_callback_error_test.rb +0 -32
- data/test/unit/transition_collection/attribute_transition_collection_with_after_callback_halt_test.rb +0 -33
- data/test/unit/transition_collection/attribute_transition_collection_with_around_after_yield_callback_error_test.rb +0 -32
- data/test/unit/transition_collection/attribute_transition_collection_with_around_callback_after_yield_error_test.rb +0 -32
- data/test/unit/transition_collection/attribute_transition_collection_with_around_callback_after_yield_halt_test.rb +0 -33
- data/test/unit/transition_collection/attribute_transition_collection_with_around_callback_before_yield_halt_test.rb +0 -33
- data/test/unit/transition_collection/attribute_transition_collection_with_before_callback_error_test.rb +0 -32
- data/test/unit/transition_collection/attribute_transition_collection_with_before_callback_halt_test.rb +0 -33
- data/test/unit/transition_collection/attribute_transition_collection_with_callbacks_test.rb +0 -68
- data/test/unit/transition_collection/attribute_transition_collection_with_event_transitions_test.rb +0 -41
- data/test/unit/transition_collection/attribute_transition_collection_with_events_test.rb +0 -44
- data/test/unit/transition_collection/attribute_transition_collection_with_skipped_after_callbacks_test.rb +0 -42
- data/test/unit/transition_collection/transition_collection_by_default_test.rb +0 -23
- data/test/unit/transition_collection/transition_collection_empty_with_block_test.rb +0 -23
- data/test/unit/transition_collection/transition_collection_empty_without_block_test.rb +0 -12
- data/test/unit/transition_collection/transition_collection_invalid_test.rb +0 -21
- data/test/unit/transition_collection/transition_collection_partial_invalid_test.rb +0 -69
- data/test/unit/transition_collection/transition_collection_test.rb +0 -26
- data/test/unit/transition_collection/transition_collection_valid_test.rb +0 -57
- data/test/unit/transition_collection/transition_collection_with_action_error_test.rb +0 -66
- data/test/unit/transition_collection/transition_collection_with_action_failed_test.rb +0 -60
- data/test/unit/transition_collection/transition_collection_with_action_hook_and_block_test.rb +0 -17
- data/test/unit/transition_collection/transition_collection_with_action_hook_and_skipped_action_test.rb +0 -17
- data/test/unit/transition_collection/transition_collection_with_action_hook_and_skipped_after_callbacks_test.rb +0 -37
- data/test/unit/transition_collection/transition_collection_with_action_hook_base_test.rb +0 -34
- data/test/unit/transition_collection/transition_collection_with_action_hook_error_test.rb +0 -29
- data/test/unit/transition_collection/transition_collection_with_action_hook_invalid_test.rb +0 -17
- data/test/unit/transition_collection/transition_collection_with_action_hook_multiple_test.rb +0 -79
- data/test/unit/transition_collection/transition_collection_with_action_hook_test.rb +0 -45
- data/test/unit/transition_collection/transition_collection_with_action_hook_with_different_actions_test.rb +0 -48
- data/test/unit/transition_collection/transition_collection_with_action_hook_with_nil_action_test.rb +0 -42
- data/test/unit/transition_collection/transition_collection_with_after_callback_halt_test.rb +0 -47
- data/test/unit/transition_collection/transition_collection_with_before_callback_halt_test.rb +0 -51
- data/test/unit/transition_collection/transition_collection_with_block_test.rb +0 -46
- data/test/unit/transition_collection/transition_collection_with_callbacks_test.rb +0 -135
- data/test/unit/transition_collection/transition_collection_with_different_actions_test.rb +0 -189
- data/test/unit/transition_collection/transition_collection_with_duplicate_actions_test.rb +0 -48
- data/test/unit/transition_collection/transition_collection_with_empty_actions_test.rb +0 -41
- data/test/unit/transition_collection/transition_collection_with_mixed_actions_test.rb +0 -41
- data/test/unit/transition_collection/transition_collection_with_skipped_actions_and_block_test.rb +0 -34
- data/test/unit/transition_collection/transition_collection_with_skipped_actions_test.rb +0 -69
- data/test/unit/transition_collection/transition_collection_with_skipped_after_callbacks_and_around_callbacks_test.rb +0 -53
- data/test/unit/transition_collection/transition_collection_with_skipped_after_callbacks_test.rb +0 -34
- data/test/unit/transition_collection/transition_collection_with_transactions_test.rb +0 -65
- data/test/unit/transition_collection/transition_collection_without_transactions_test.rb +0 -29
@@ -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).
|
@@ -452,9 +452,9 @@ module StateMachines
|
|
452
452
|
attr_accessor :ignore_method_conflicts
|
453
453
|
end
|
454
454
|
@default_messages = {
|
455
|
-
:
|
456
|
-
:
|
457
|
-
:
|
455
|
+
invalid: 'is invalid',
|
456
|
+
invalid_event: 'cannot transition when %s',
|
457
|
+
invalid_transition: 'cannot transition via "%1$s"'
|
458
458
|
}
|
459
459
|
|
460
460
|
# Whether to ignore any conflicts that are detected for helper methods that
|
@@ -479,12 +479,12 @@ module StateMachines
|
|
479
479
|
# * Event transitions (:to, :from, and :except_from options)
|
480
480
|
# * Transition callbacks (:to, :from, :except_to, and :except_from options)
|
481
481
|
# * Unreferenced states (using +other_states+ helper)
|
482
|
-
#
|
482
|
+
#
|
483
483
|
# These are sorted, by default, in the order in which they were referenced.
|
484
484
|
attr_reader :states
|
485
485
|
|
486
486
|
# The callbacks to invoke before/after a transition is performed
|
487
|
-
#
|
487
|
+
#
|
488
488
|
# Maps :before => callbacks and :after => callbacks
|
489
489
|
attr_reader :callbacks
|
490
490
|
|
@@ -517,14 +517,14 @@ module StateMachines
|
|
517
517
|
end
|
518
518
|
|
519
519
|
# Add machine-wide defaults
|
520
|
-
options = {:
|
520
|
+
options = {use_transactions: true, initialize: true}.merge(options)
|
521
521
|
|
522
522
|
# Set machine configuration
|
523
523
|
@name = args.first || :state
|
524
524
|
@attribute = options[:attribute] || @name
|
525
525
|
@events = EventCollection.new(self)
|
526
526
|
@states = StateCollection.new(self)
|
527
|
-
@callbacks = {:
|
527
|
+
@callbacks = {before: [], after: [], failure: []}
|
528
528
|
@namespace = options[:namespace]
|
529
529
|
@messages = options[:messages] || {}
|
530
530
|
@action = options[:action]
|
@@ -556,7 +556,7 @@ module StateMachines
|
|
556
556
|
@events.machine = self
|
557
557
|
@states = @states.dup
|
558
558
|
@states.machine = self
|
559
|
-
@callbacks = {:
|
559
|
+
@callbacks = {before: @callbacks[:before].dup, after: @callbacks[:after].dup, failure: @callbacks[:failure].dup}
|
560
560
|
end
|
561
561
|
|
562
562
|
# Sets the class which is the owner of this state machine. Any methods
|
@@ -566,7 +566,7 @@ module StateMachines
|
|
566
566
|
@owner_class = klass
|
567
567
|
|
568
568
|
# Create modules for extending the class with state/event-specific methods
|
569
|
-
@helper_modules = helper_modules = {:
|
569
|
+
@helper_modules = helper_modules = {instance: HelperModule.new(self, :instance), class: HelperModule.new(self, :class)}
|
570
570
|
owner_class.class_eval do
|
571
571
|
extend helper_modules[:class]
|
572
572
|
include helper_modules[:instance]
|
@@ -612,35 +612,35 @@ module StateMachines
|
|
612
612
|
# Gets the initial state of the machine for the given object. If a dynamic
|
613
613
|
# initial state was configured for this machine, then the object will be
|
614
614
|
# passed into the lambda block to help determine the actual state.
|
615
|
-
#
|
615
|
+
#
|
616
616
|
# == Examples
|
617
|
-
#
|
617
|
+
#
|
618
618
|
# With a static initial state:
|
619
|
-
#
|
619
|
+
#
|
620
620
|
# class Vehicle
|
621
621
|
# state_machine :initial => :parked do
|
622
622
|
# ...
|
623
623
|
# end
|
624
624
|
# end
|
625
|
-
#
|
625
|
+
#
|
626
626
|
# vehicle = Vehicle.new
|
627
627
|
# Vehicle.state_machine.initial_state(vehicle) # => #<StateMachines::State name=:parked value="parked" initial=true>
|
628
|
-
#
|
628
|
+
#
|
629
629
|
# With a dynamic initial state:
|
630
|
-
#
|
630
|
+
#
|
631
631
|
# class Vehicle
|
632
632
|
# attr_accessor :force_idle
|
633
|
-
#
|
633
|
+
#
|
634
634
|
# state_machine :initial => lambda {|vehicle| vehicle.force_idle ? :idling : :parked} do
|
635
635
|
# ...
|
636
636
|
# end
|
637
637
|
# end
|
638
|
-
#
|
638
|
+
#
|
639
639
|
# vehicle = Vehicle.new
|
640
|
-
#
|
640
|
+
#
|
641
641
|
# vehicle.force_idle = true
|
642
642
|
# Vehicle.state_machine.initial_state(vehicle) # => #<StateMachines::State name=:idling value="idling" initial=false>
|
643
|
-
#
|
643
|
+
#
|
644
644
|
# vehicle.force_idle = false
|
645
645
|
# Vehicle.state_machine.initial_state(vehicle) # => #<StateMachines::State name=:parked value="parked" initial=false>
|
646
646
|
def initial_state(object)
|
@@ -654,7 +654,7 @@ module StateMachines
|
|
654
654
|
|
655
655
|
# Initializes the state on the given object. Initial values are only set if
|
656
656
|
# the machine's attribute hasn't been previously initialized.
|
657
|
-
#
|
657
|
+
#
|
658
658
|
# Configuration options:
|
659
659
|
# * <tt>:force</tt> - Whether to initialize the state regardless of its
|
660
660
|
# current value
|
@@ -665,7 +665,7 @@ module StateMachines
|
|
665
665
|
if state && (options[:force] || initialize_state?(object))
|
666
666
|
value = state.value
|
667
667
|
|
668
|
-
if hash = options[:to]
|
668
|
+
if (hash = options[:to])
|
669
669
|
hash[attribute.to_s] = value
|
670
670
|
else
|
671
671
|
write(object, :state, value)
|
@@ -682,61 +682,61 @@ module StateMachines
|
|
682
682
|
# Defines a new helper method in an instance or class scope with the given
|
683
683
|
# name. If the method is already defined in the scope, then this will not
|
684
684
|
# override it.
|
685
|
-
#
|
685
|
+
#
|
686
686
|
# If passing in a block, there are two side effects to be aware of
|
687
687
|
# 1. The method cannot be chained, meaning that the block cannot call +super+
|
688
688
|
# 2. If the method is already defined in an ancestor, then it will not get
|
689
689
|
# overridden and a warning will be output.
|
690
|
-
#
|
690
|
+
#
|
691
691
|
# Example:
|
692
|
-
#
|
692
|
+
#
|
693
693
|
# # Instance helper
|
694
694
|
# machine.define_helper(:instance, :state_name) do |machine, object|
|
695
695
|
# machine.states.match(object).name
|
696
696
|
# end
|
697
|
-
#
|
697
|
+
#
|
698
698
|
# # Class helper
|
699
699
|
# machine.define_helper(:class, :state_machine_name) do |machine, klass|
|
700
700
|
# "State"
|
701
701
|
# end
|
702
|
-
#
|
702
|
+
#
|
703
703
|
# You can also define helpers using string evaluation like so:
|
704
|
-
#
|
704
|
+
#
|
705
705
|
# # Instance helper
|
706
706
|
# machine.define_helper :instance, <<-end_eval, __FILE__, __LINE__ + 1
|
707
707
|
# def state_name
|
708
708
|
# self.class.state_machine(:state).states.match(self).name
|
709
709
|
# end
|
710
710
|
# end_eval
|
711
|
-
#
|
711
|
+
#
|
712
712
|
# # Class helper
|
713
713
|
# machine.define_helper :class, <<-end_eval, __FILE__, __LINE__ + 1
|
714
714
|
# def state_machine_name
|
715
715
|
# "State"
|
716
716
|
# end
|
717
717
|
# end_eval
|
718
|
-
def define_helper(scope, method, *args, &block)
|
718
|
+
def define_helper(scope, method, *args, **kwargs, &block)
|
719
719
|
helper_module = @helper_modules.fetch(scope)
|
720
720
|
|
721
721
|
if block_given?
|
722
|
-
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))
|
723
723
|
ancestor_name = conflicting_ancestor.name && !conflicting_ancestor.name.empty? ? conflicting_ancestor.name : conflicting_ancestor.to_s
|
724
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."
|
725
725
|
else
|
726
726
|
name = self.name
|
727
727
|
helper_module.class_eval do
|
728
|
-
define_method(method) do |*block_args|
|
729
|
-
block.call((scope == :instance ? self.class : self).state_machine(name), self, *block_args)
|
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)
|
730
730
|
end
|
731
731
|
end
|
732
732
|
end
|
733
733
|
else
|
734
|
-
helper_module.class_eval(method, *args)
|
734
|
+
helper_module.class_eval(method, *args, **kwargs)
|
735
735
|
end
|
736
736
|
end
|
737
737
|
|
738
738
|
# Customizes the definition of one or more states in the machine.
|
739
|
-
#
|
739
|
+
#
|
740
740
|
# Configuration options:
|
741
741
|
# * <tt>:value</tt> - The actual value to store when an object transitions
|
742
742
|
# to the state. Default is the name (stringified).
|
@@ -748,13 +748,13 @@ module StateMachines
|
|
748
748
|
# * <tt>:human_name</tt> - The human-readable version of this state's name.
|
749
749
|
# By default, this is either defined by the integration or stringifies the
|
750
750
|
# name and converts underscores to spaces.
|
751
|
-
#
|
751
|
+
#
|
752
752
|
# == Customizing the stored value
|
753
|
-
#
|
753
|
+
#
|
754
754
|
# Whenever a state is automatically discovered in the state machine, its
|
755
755
|
# default value is assumed to be the stringified version of the name. For
|
756
756
|
# example,
|
757
|
-
#
|
757
|
+
#
|
758
758
|
# class Vehicle
|
759
759
|
# state_machine :initial => :parked do
|
760
760
|
# event :ignite do
|
@@ -762,124 +762,124 @@ module StateMachines
|
|
762
762
|
# end
|
763
763
|
# end
|
764
764
|
# end
|
765
|
-
#
|
765
|
+
#
|
766
766
|
# In the above state machine, there are two states automatically discovered:
|
767
767
|
# :parked and :idling. These states, by default, will store their stringified
|
768
768
|
# equivalents when an object moves into that state (e.g. "parked" / "idling").
|
769
|
-
#
|
769
|
+
#
|
770
770
|
# For legacy systems or when tying state machines into existing frameworks,
|
771
771
|
# it's oftentimes necessary to need to store a different value for a state
|
772
772
|
# than the default. In order to continue taking advantage of an expressive
|
773
773
|
# state machine and helper methods, every defined state can be re-configured
|
774
774
|
# with a custom stored value. For example,
|
775
|
-
#
|
775
|
+
#
|
776
776
|
# class Vehicle
|
777
777
|
# state_machine :initial => :parked do
|
778
778
|
# event :ignite do
|
779
779
|
# transition :parked => :idling
|
780
780
|
# end
|
781
|
-
#
|
781
|
+
#
|
782
782
|
# state :idling, :value => 'IDLING'
|
783
783
|
# state :parked, :value => 'PARKED
|
784
784
|
# end
|
785
785
|
# end
|
786
|
-
#
|
786
|
+
#
|
787
787
|
# This is also useful if being used in association with a database and,
|
788
788
|
# instead of storing the state name in a column, you want to store the
|
789
789
|
# state's foreign key:
|
790
|
-
#
|
790
|
+
#
|
791
791
|
# class VehicleState < ActiveRecord::Base
|
792
792
|
# end
|
793
|
-
#
|
793
|
+
#
|
794
794
|
# class Vehicle < ActiveRecord::Base
|
795
795
|
# state_machine :attribute => :state_id, :initial => :parked do
|
796
796
|
# event :ignite do
|
797
797
|
# transition :parked => :idling
|
798
798
|
# end
|
799
|
-
#
|
799
|
+
#
|
800
800
|
# states.each do |state|
|
801
801
|
# self.state(state.name, :value => lambda { VehicleState.find_by_name(state.name.to_s).id }, :cache => true)
|
802
802
|
# end
|
803
803
|
# end
|
804
804
|
# end
|
805
|
-
#
|
805
|
+
#
|
806
806
|
# In the above example, each known state is configured to store it's
|
807
807
|
# associated database id in the +state_id+ attribute. Also, notice that a
|
808
808
|
# lambda block is used to define the state's value. This is required in
|
809
809
|
# situations (like testing) where the model is loaded without any existing
|
810
810
|
# data (i.e. no VehicleState records available).
|
811
|
-
#
|
811
|
+
#
|
812
812
|
# One caveat to the above example is to keep performance in mind. To avoid
|
813
813
|
# constant db hits for looking up the VehicleState ids, the value is cached
|
814
814
|
# by specifying the <tt>:cache</tt> option. Alternatively, a custom
|
815
815
|
# caching strategy can be used like so:
|
816
|
-
#
|
816
|
+
#
|
817
817
|
# class VehicleState < ActiveRecord::Base
|
818
818
|
# cattr_accessor :cache_store
|
819
819
|
# self.cache_store = ActiveSupport::Cache::MemoryStore.new
|
820
|
-
#
|
820
|
+
#
|
821
821
|
# def self.find_by_name(name)
|
822
822
|
# cache_store.fetch(name) { find(:first, :conditions => {:name => name}) }
|
823
823
|
# end
|
824
824
|
# end
|
825
|
-
#
|
825
|
+
#
|
826
826
|
# === Dynamic values
|
827
|
-
#
|
827
|
+
#
|
828
828
|
# In addition to customizing states with other value types, lambda blocks
|
829
829
|
# can also be specified to allow for a state's value to be determined
|
830
830
|
# dynamically at runtime. For example,
|
831
|
-
#
|
831
|
+
#
|
832
832
|
# class Vehicle
|
833
833
|
# state_machine :purchased_at, :initial => :available do
|
834
834
|
# event :purchase do
|
835
835
|
# transition all => :purchased
|
836
836
|
# end
|
837
|
-
#
|
837
|
+
#
|
838
838
|
# event :restock do
|
839
839
|
# transition all => :available
|
840
840
|
# end
|
841
|
-
#
|
841
|
+
#
|
842
842
|
# state :available, :value => nil
|
843
843
|
# state :purchased, :if => lambda {|value| !value.nil?}, :value => lambda {Time.now}
|
844
844
|
# end
|
845
845
|
# end
|
846
|
-
#
|
846
|
+
#
|
847
847
|
# In the above definition, the <tt>:purchased</tt> state is customized with
|
848
848
|
# both a dynamic value *and* a value matcher.
|
849
|
-
#
|
849
|
+
#
|
850
850
|
# When an object transitions to the purchased state, the value's lambda
|
851
851
|
# block will be called. This will get the current time and store it in the
|
852
852
|
# object's +purchased_at+ attribute.
|
853
|
-
#
|
853
|
+
#
|
854
854
|
# *Note* that the custom matcher is very important here. Since there's no
|
855
855
|
# way for the state machine to figure out an object's state when it's set to
|
856
856
|
# a runtime value, it must be explicitly defined. If the <tt>:if</tt> option
|
857
857
|
# were not configured for the state, then an ArgumentError exception would
|
858
858
|
# be raised at runtime, indicating that the state machine could not figure
|
859
859
|
# out what the current state of the object was.
|
860
|
-
#
|
860
|
+
#
|
861
861
|
# == Behaviors
|
862
|
-
#
|
862
|
+
#
|
863
863
|
# Behaviors define a series of methods to mixin with objects when the current
|
864
864
|
# state matches the given one(s). This allows instance methods to behave
|
865
865
|
# a specific way depending on what the value of the object's state is.
|
866
|
-
#
|
866
|
+
#
|
867
867
|
# For example,
|
868
|
-
#
|
868
|
+
#
|
869
869
|
# class Vehicle
|
870
870
|
# attr_accessor :driver
|
871
871
|
# attr_accessor :passenger
|
872
|
-
#
|
872
|
+
#
|
873
873
|
# state_machine :initial => :parked do
|
874
874
|
# event :ignite do
|
875
875
|
# transition :parked => :idling
|
876
876
|
# end
|
877
|
-
#
|
877
|
+
#
|
878
878
|
# state :parked do
|
879
879
|
# def speed
|
880
880
|
# 0
|
881
881
|
# end
|
882
|
-
#
|
882
|
+
#
|
883
883
|
# def rotate_driver
|
884
884
|
# driver = self.driver
|
885
885
|
# self.driver = passenger
|
@@ -887,97 +887,97 @@ module StateMachines
|
|
887
887
|
# true
|
888
888
|
# end
|
889
889
|
# end
|
890
|
-
#
|
890
|
+
#
|
891
891
|
# state :idling, :first_gear do
|
892
892
|
# def speed
|
893
893
|
# 20
|
894
894
|
# end
|
895
|
-
#
|
895
|
+
#
|
896
896
|
# def rotate_driver
|
897
897
|
# self.state = 'parked'
|
898
898
|
# rotate_driver
|
899
899
|
# end
|
900
900
|
# end
|
901
|
-
#
|
901
|
+
#
|
902
902
|
# other_states :backing_up
|
903
903
|
# end
|
904
904
|
# end
|
905
|
-
#
|
905
|
+
#
|
906
906
|
# In the above example, there are two dynamic behaviors defined for the
|
907
907
|
# class:
|
908
908
|
# * +speed+
|
909
909
|
# * +rotate_driver+
|
910
|
-
#
|
910
|
+
#
|
911
911
|
# Each of these behaviors are instance methods on the Vehicle class. However,
|
912
912
|
# which method actually gets invoked is based on the current state of the
|
913
913
|
# object. Using the above class as the example:
|
914
|
-
#
|
914
|
+
#
|
915
915
|
# vehicle = Vehicle.new
|
916
916
|
# vehicle.driver = 'John'
|
917
917
|
# vehicle.passenger = 'Jane'
|
918
|
-
#
|
918
|
+
#
|
919
919
|
# # Behaviors in the "parked" state
|
920
920
|
# vehicle.state # => "parked"
|
921
921
|
# vehicle.speed # => 0
|
922
922
|
# vehicle.rotate_driver # => true
|
923
923
|
# vehicle.driver # => "Jane"
|
924
924
|
# vehicle.passenger # => "John"
|
925
|
-
#
|
925
|
+
#
|
926
926
|
# vehicle.ignite # => true
|
927
|
-
#
|
927
|
+
#
|
928
928
|
# # Behaviors in the "idling" state
|
929
929
|
# vehicle.state # => "idling"
|
930
930
|
# vehicle.speed # => 20
|
931
931
|
# vehicle.rotate_driver # => true
|
932
932
|
# vehicle.driver # => "John"
|
933
933
|
# vehicle.passenger # => "Jane"
|
934
|
-
#
|
934
|
+
#
|
935
935
|
# As can be seen, both the +speed+ and +rotate_driver+ instance method
|
936
936
|
# implementations changed how they behave based on what the current state
|
937
937
|
# of the vehicle was.
|
938
|
-
#
|
938
|
+
#
|
939
939
|
# === Invalid behaviors
|
940
|
-
#
|
940
|
+
#
|
941
941
|
# If a specific behavior has not been defined for a state, then a
|
942
942
|
# NoMethodError exception will be raised, indicating that that method would
|
943
943
|
# not normally exist for an object with that state.
|
944
|
-
#
|
944
|
+
#
|
945
945
|
# Using the example from before:
|
946
|
-
#
|
946
|
+
#
|
947
947
|
# vehicle = Vehicle.new
|
948
948
|
# vehicle.state = 'backing_up'
|
949
949
|
# vehicle.speed # => NoMethodError: undefined method 'speed' for #<Vehicle:0xb7d296ac> in state "backing_up"
|
950
|
-
#
|
950
|
+
#
|
951
951
|
# === Using matchers
|
952
|
-
#
|
952
|
+
#
|
953
953
|
# The +all+ / +any+ matchers can be used to easily define behaviors for a
|
954
954
|
# group of states. Note, however, that you cannot use these matchers to
|
955
955
|
# set configurations for states. Behaviors using these matchers can be
|
956
956
|
# defined at any point in the state machine and will always get applied to
|
957
957
|
# the proper states.
|
958
|
-
#
|
958
|
+
#
|
959
959
|
# For example:
|
960
|
-
#
|
960
|
+
#
|
961
961
|
# state_machine :initial => :parked do
|
962
962
|
# ...
|
963
|
-
#
|
963
|
+
#
|
964
964
|
# state all - [:parked, :idling, :stalled] do
|
965
965
|
# validates_presence_of :speed
|
966
|
-
#
|
966
|
+
#
|
967
967
|
# def speed
|
968
968
|
# gear * 10
|
969
969
|
# end
|
970
970
|
# end
|
971
971
|
# end
|
972
|
-
#
|
972
|
+
#
|
973
973
|
# == State-aware class methods
|
974
|
-
#
|
974
|
+
#
|
975
975
|
# In addition to defining scopes for instance methods that are state-aware,
|
976
976
|
# the same can be done for certain types of class methods.
|
977
|
-
#
|
977
|
+
#
|
978
978
|
# Some libraries have support for class-level methods that only run certain
|
979
979
|
# behaviors based on a conditions hash passed in. For example:
|
980
|
-
#
|
980
|
+
#
|
981
981
|
# class Vehicle < ActiveRecord::Base
|
982
982
|
# state_machine do
|
983
983
|
# ...
|
@@ -987,19 +987,19 @@ module StateMachines
|
|
987
987
|
# end
|
988
988
|
# end
|
989
989
|
# end
|
990
|
-
#
|
990
|
+
#
|
991
991
|
# In the above ActiveRecord model, two validations have been defined which
|
992
992
|
# will *only* run when the Vehicle object is in one of the three states:
|
993
993
|
# +first_gear+, +second_gear+, or +third_gear. Notice, also, that if/unless
|
994
994
|
# conditions can continue to be used.
|
995
|
-
#
|
995
|
+
#
|
996
996
|
# This functionality is not library-specific and can work for any class-level
|
997
997
|
# method that is defined like so:
|
998
|
-
#
|
998
|
+
#
|
999
999
|
# def validates_presence_of(attribute, options = {})
|
1000
1000
|
# ...
|
1001
1001
|
# end
|
1002
|
-
#
|
1002
|
+
#
|
1003
1003
|
# The minimum requirement is that the last argument in the method be an
|
1004
1004
|
# options hash which contains at least <tt>:if</tt> condition support.
|
1005
1005
|
def state(*names, &block)
|
@@ -1014,6 +1014,7 @@ module StateMachines
|
|
1014
1014
|
# Add any states referenced in the matcher. When matchers are used,
|
1015
1015
|
# states are not allowed to be configured.
|
1016
1016
|
raise ArgumentError, "Cannot configure states when using matchers (using #{options.inspect})" if options.any?
|
1017
|
+
|
1017
1018
|
states = add_states(names.first.values)
|
1018
1019
|
else
|
1019
1020
|
states = add_states(names)
|
@@ -1037,15 +1038,15 @@ module StateMachines
|
|
1037
1038
|
alias_method :other_states, :state
|
1038
1039
|
|
1039
1040
|
# Gets the current value stored in the given object's attribute.
|
1040
|
-
#
|
1041
|
+
#
|
1041
1042
|
# For example,
|
1042
|
-
#
|
1043
|
+
#
|
1043
1044
|
# class Vehicle
|
1044
1045
|
# state_machine :initial => :parked do
|
1045
1046
|
# ...
|
1046
1047
|
# end
|
1047
1048
|
# end
|
1048
|
-
#
|
1049
|
+
#
|
1049
1050
|
# vehicle = Vehicle.new # => #<Vehicle:0xb7d94ab0 @state="parked">
|
1050
1051
|
# Vehicle.state_machine.read(vehicle, :state) # => "parked" # Equivalent to vehicle.state
|
1051
1052
|
# Vehicle.state_machine.read(vehicle, :event) # => nil # Equivalent to vehicle.state_event
|
@@ -1059,15 +1060,15 @@ module StateMachines
|
|
1059
1060
|
end
|
1060
1061
|
|
1061
1062
|
# Sets a new value in the given object's attribute.
|
1062
|
-
#
|
1063
|
+
#
|
1063
1064
|
# For example,
|
1064
|
-
#
|
1065
|
+
#
|
1065
1066
|
# class Vehicle
|
1066
1067
|
# state_machine :initial => :parked do
|
1067
1068
|
# ...
|
1068
1069
|
# end
|
1069
1070
|
# end
|
1070
|
-
#
|
1071
|
+
#
|
1071
1072
|
# vehicle = Vehicle.new # => #<Vehicle:0xb7d94ab0 @state="parked">
|
1072
1073
|
# Vehicle.state_machine.write(vehicle, :state, 'idling') # => Equivalent to vehicle.state = 'idling'
|
1073
1074
|
# Vehicle.state_machine.write(vehicle, :event, 'park') # => Equivalent to vehicle.state_event = 'park'
|
@@ -1080,17 +1081,17 @@ module StateMachines
|
|
1080
1081
|
|
1081
1082
|
# Defines one or more events for the machine and the transitions that can
|
1082
1083
|
# be performed when those events are run.
|
1083
|
-
#
|
1084
|
+
#
|
1084
1085
|
# This method is also aliased as +on+ for improved compatibility with
|
1085
1086
|
# using a domain-specific language.
|
1086
|
-
#
|
1087
|
+
#
|
1087
1088
|
# Configuration options:
|
1088
1089
|
# * <tt>:human_name</tt> - The human-readable version of this event's name.
|
1089
1090
|
# By default, this is either defined by the integration or stringifies the
|
1090
1091
|
# name and converts underscores to spaces.
|
1091
|
-
#
|
1092
|
+
#
|
1092
1093
|
# == Instance methods
|
1093
|
-
#
|
1094
|
+
#
|
1094
1095
|
# The following instance methods are generated when a new event is defined
|
1095
1096
|
# (the "park" event is used as an example):
|
1096
1097
|
# * <tt>park(..., run_action = true)</tt> - Fires the "park" event,
|
@@ -1114,13 +1115,13 @@ module StateMachines
|
|
1114
1115
|
# object or nil if no transitions can be performed. Like <tt>can_park?</tt>
|
1115
1116
|
# this will also *not* run validations or callbacks. It will only
|
1116
1117
|
# determine if the state machine defines a valid transition for the event.
|
1117
|
-
#
|
1118
|
+
#
|
1118
1119
|
# With a namespace of "car", the above names map to the following methods:
|
1119
1120
|
# * <tt>can_park_car?</tt>
|
1120
1121
|
# * <tt>park_car_transition</tt>
|
1121
1122
|
# * <tt>park_car</tt>
|
1122
1123
|
# * <tt>park_car!</tt>
|
1123
|
-
#
|
1124
|
+
#
|
1124
1125
|
# The <tt>can_park?</tt> and <tt>park_transition</tt> helpers both take an
|
1125
1126
|
# optional set of requirements for determining what transitions are available
|
1126
1127
|
# for the current object. These requirements include:
|
@@ -1130,58 +1131,58 @@ module StateMachines
|
|
1130
1131
|
# specified, then this will match any to state.
|
1131
1132
|
# * <tt>:guard</tt> - Whether to guard transitions with the if/unless
|
1132
1133
|
# conditionals defined for each one. Default is true.
|
1133
|
-
#
|
1134
|
+
#
|
1134
1135
|
# == Defining transitions
|
1135
|
-
#
|
1136
|
+
#
|
1136
1137
|
# +event+ requires a block which allows you to define the possible
|
1137
1138
|
# transitions that can happen as a result of that event. For example,
|
1138
|
-
#
|
1139
|
+
#
|
1139
1140
|
# event :park, :stop do
|
1140
1141
|
# transition :idling => :parked
|
1141
1142
|
# end
|
1142
|
-
#
|
1143
|
+
#
|
1143
1144
|
# event :first_gear do
|
1144
1145
|
# transition :parked => :first_gear, :if => :seatbelt_on?
|
1145
1146
|
# transition :parked => same # Allow to loopback if seatbelt is off
|
1146
1147
|
# end
|
1147
|
-
#
|
1148
|
+
#
|
1148
1149
|
# See StateMachines::Event#transition for more information on
|
1149
1150
|
# the possible options that can be passed in.
|
1150
|
-
#
|
1151
|
+
#
|
1151
1152
|
# *Note* that this block is executed within the context of the actual event
|
1152
1153
|
# object. As a result, you will not be able to reference any class methods
|
1153
1154
|
# on the model without referencing the class itself. For example,
|
1154
|
-
#
|
1155
|
+
#
|
1155
1156
|
# class Vehicle
|
1156
1157
|
# def self.safe_states
|
1157
1158
|
# [:parked, :idling, :stalled]
|
1158
1159
|
# end
|
1159
|
-
#
|
1160
|
+
#
|
1160
1161
|
# state_machine do
|
1161
1162
|
# event :park do
|
1162
1163
|
# transition Vehicle.safe_states => :parked
|
1163
1164
|
# end
|
1164
1165
|
# end
|
1165
|
-
# end
|
1166
|
-
#
|
1166
|
+
# end
|
1167
|
+
#
|
1167
1168
|
# == Overriding the event method
|
1168
|
-
#
|
1169
|
+
#
|
1169
1170
|
# By default, this will define an instance method (with the same name as the
|
1170
1171
|
# event) that will fire the next possible transition for that. Although the
|
1171
1172
|
# +before_transition+, +after_transition+, and +around_transition+ hooks
|
1172
1173
|
# allow you to define behavior that gets executed as a result of the event's
|
1173
1174
|
# transition, you can also override the event method in order to have a
|
1174
1175
|
# little more fine-grained control.
|
1175
|
-
#
|
1176
|
+
#
|
1176
1177
|
# For example:
|
1177
|
-
#
|
1178
|
+
#
|
1178
1179
|
# class Vehicle
|
1179
1180
|
# state_machine do
|
1180
1181
|
# event :park do
|
1181
1182
|
# ...
|
1182
1183
|
# end
|
1183
1184
|
# end
|
1184
|
-
#
|
1185
|
+
#
|
1185
1186
|
# def park(*)
|
1186
1187
|
# take_deep_breath # Executes before the transition (and before_transition hooks) even if no transition is possible
|
1187
1188
|
# if result = super # Runs the transition and all before/after/around hooks
|
@@ -1190,33 +1191,33 @@ module StateMachines
|
|
1190
1191
|
# result
|
1191
1192
|
# end
|
1192
1193
|
# end
|
1193
|
-
#
|
1194
|
+
#
|
1194
1195
|
# There are a few important things to note here. First, the method
|
1195
1196
|
# signature is defined with an unlimited argument list in order to allow
|
1196
1197
|
# callers to continue passing arguments that are expected by state_machine.
|
1197
1198
|
# For example, it will still allow calls to +park+ with a single parameter
|
1198
1199
|
# for skipping the configured action.
|
1199
|
-
#
|
1200
|
+
#
|
1200
1201
|
# Second, the overridden event method must call +super+ in order to run the
|
1201
1202
|
# logic for running the next possible transition. In order to remain
|
1202
1203
|
# consistent with other events, the result of +super+ is returned.
|
1203
|
-
#
|
1204
|
+
#
|
1204
1205
|
# Third, any behavior defined in this method will *not* get executed if
|
1205
1206
|
# you're taking advantage of attribute-based event transitions. For example:
|
1206
|
-
#
|
1207
|
+
#
|
1207
1208
|
# vehicle = Vehicle.new
|
1208
1209
|
# vehicle.state_event = 'park'
|
1209
1210
|
# vehicle.save
|
1210
|
-
#
|
1211
|
+
#
|
1211
1212
|
# In this case, the +park+ event will run the before/after/around transition
|
1212
1213
|
# hooks and transition the state, but the behavior defined in the overriden
|
1213
1214
|
# +park+ method will *not* be executed.
|
1214
|
-
#
|
1215
|
+
#
|
1215
1216
|
# == Defining additional arguments
|
1216
|
-
#
|
1217
|
+
#
|
1217
1218
|
# Additional arguments can be passed into events and accessed by transition
|
1218
1219
|
# hooks like so:
|
1219
|
-
#
|
1220
|
+
#
|
1220
1221
|
# class Vehicle
|
1221
1222
|
# state_machine do
|
1222
1223
|
# after_transition :on => :park do |vehicle, transition|
|
@@ -1224,80 +1225,80 @@ module StateMachines
|
|
1224
1225
|
# ...
|
1225
1226
|
# end
|
1226
1227
|
# after_transition :on => :park, :do => :take_deep_breath
|
1227
|
-
#
|
1228
|
+
#
|
1228
1229
|
# event :park do
|
1229
1230
|
# ...
|
1230
1231
|
# end
|
1231
|
-
#
|
1232
|
+
#
|
1232
1233
|
# def take_deep_breath(transition)
|
1233
1234
|
# kind = *transition.args # :parallel
|
1234
1235
|
# ...
|
1235
1236
|
# end
|
1236
1237
|
# end
|
1237
1238
|
# end
|
1238
|
-
#
|
1239
|
+
#
|
1239
1240
|
# vehicle = Vehicle.new
|
1240
1241
|
# vehicle.park(:parallel)
|
1241
|
-
#
|
1242
|
+
#
|
1242
1243
|
# *Remember* that if the last argument is a boolean, it will be used as the
|
1243
1244
|
# +run_action+ parameter to the event action. Using the +park+ action
|
1244
1245
|
# example from above, you can might call it like so:
|
1245
|
-
#
|
1246
|
+
#
|
1246
1247
|
# vehicle.park # => Uses default args and runs machine action
|
1247
1248
|
# vehicle.park(:parallel) # => Specifies the +kind+ argument and runs the machine action
|
1248
1249
|
# vehicle.park(:parallel, false) # => Specifies the +kind+ argument and *skips* the machine action
|
1249
|
-
#
|
1250
|
+
#
|
1250
1251
|
# If you decide to override the +park+ event method *and* define additional
|
1251
1252
|
# arguments, you can do so as shown below:
|
1252
|
-
#
|
1253
|
+
#
|
1253
1254
|
# class Vehicle
|
1254
1255
|
# state_machine do
|
1255
1256
|
# event :park do
|
1256
1257
|
# ...
|
1257
1258
|
# end
|
1258
1259
|
# end
|
1259
|
-
#
|
1260
|
+
#
|
1260
1261
|
# def park(kind = :parallel, *args)
|
1261
1262
|
# take_deep_breath if kind == :parallel
|
1262
1263
|
# super
|
1263
1264
|
# end
|
1264
1265
|
# end
|
1265
|
-
#
|
1266
|
+
#
|
1266
1267
|
# Note that +super+ is called instead of <tt>super(*args)</tt>. This allow
|
1267
1268
|
# the entire arguments list to be accessed by transition callbacks through
|
1268
1269
|
# StateMachines::Transition#args.
|
1269
|
-
#
|
1270
|
+
#
|
1270
1271
|
# === Using matchers
|
1271
|
-
#
|
1272
|
+
#
|
1272
1273
|
# The +all+ / +any+ matchers can be used to easily execute blocks for a
|
1273
1274
|
# group of events. Note, however, that you cannot use these matchers to
|
1274
1275
|
# set configurations for events. Blocks using these matchers can be
|
1275
1276
|
# defined at any point in the state machine and will always get applied to
|
1276
1277
|
# the proper events.
|
1277
|
-
#
|
1278
|
+
#
|
1278
1279
|
# For example:
|
1279
|
-
#
|
1280
|
+
#
|
1280
1281
|
# state_machine :initial => :parked do
|
1281
1282
|
# ...
|
1282
|
-
#
|
1283
|
+
#
|
1283
1284
|
# event all - [:crash] do
|
1284
1285
|
# transition :stalled => :parked
|
1285
1286
|
# end
|
1286
1287
|
# end
|
1287
|
-
#
|
1288
|
+
#
|
1288
1289
|
# == Example
|
1289
|
-
#
|
1290
|
+
#
|
1290
1291
|
# class Vehicle
|
1291
1292
|
# state_machine do
|
1292
1293
|
# # The park, stop, and halt events will all share the given transitions
|
1293
1294
|
# event :park, :stop, :halt do
|
1294
1295
|
# transition [:idling, :backing_up] => :parked
|
1295
1296
|
# end
|
1296
|
-
#
|
1297
|
+
#
|
1297
1298
|
# event :stop do
|
1298
1299
|
# transition :first_gear => :idling
|
1299
1300
|
# end
|
1300
|
-
#
|
1301
|
+
#
|
1301
1302
|
# event :ignite do
|
1302
1303
|
# transition :parked => :idling
|
1303
1304
|
# transition :idling => same # Allow ignite while still idling
|
@@ -1316,6 +1317,7 @@ module StateMachines
|
|
1316
1317
|
# Add any events referenced in the matcher. When matchers are used,
|
1317
1318
|
# events are not allowed to be configured.
|
1318
1319
|
raise ArgumentError, "Cannot configure events when using matchers (using #{options.inspect})" if options.any?
|
1320
|
+
|
1319
1321
|
events = add_events(names.first.values)
|
1320
1322
|
else
|
1321
1323
|
events = add_events(names)
|
@@ -1336,45 +1338,45 @@ module StateMachines
|
|
1336
1338
|
|
1337
1339
|
# Creates a new transition that determines what to change the current state
|
1338
1340
|
# to when an event fires.
|
1339
|
-
#
|
1341
|
+
#
|
1340
1342
|
# == Defining transitions
|
1341
|
-
#
|
1343
|
+
#
|
1342
1344
|
# The options for a new transition uses the Hash syntax to map beginning
|
1343
1345
|
# states to ending states. For example,
|
1344
|
-
#
|
1346
|
+
#
|
1345
1347
|
# transition :parked => :idling, :idling => :first_gear, :on => :ignite
|
1346
|
-
#
|
1348
|
+
#
|
1347
1349
|
# In this case, when the +ignite+ event is fired, this transition will cause
|
1348
1350
|
# the state to be +idling+ if it's current state is +parked+ or +first_gear+
|
1349
1351
|
# if it's current state is +idling+.
|
1350
|
-
#
|
1352
|
+
#
|
1351
1353
|
# To help define these implicit transitions, a set of helpers are available
|
1352
1354
|
# for slightly more complex matching:
|
1353
1355
|
# * <tt>all</tt> - Matches every state in the machine
|
1354
1356
|
# * <tt>all - [:parked, :idling, ...]</tt> - Matches every state except those specified
|
1355
1357
|
# * <tt>any</tt> - An alias for +all+ (matches every state in the machine)
|
1356
1358
|
# * <tt>same</tt> - Matches the same state being transitioned from
|
1357
|
-
#
|
1359
|
+
#
|
1358
1360
|
# See StateMachines::MatcherHelpers for more information.
|
1359
|
-
#
|
1361
|
+
#
|
1360
1362
|
# Examples:
|
1361
|
-
#
|
1363
|
+
#
|
1362
1364
|
# transition all => nil, :on => :ignite # Transitions to nil regardless of the current state
|
1363
1365
|
# transition all => :idling, :on => :ignite # Transitions to :idling regardless of the current state
|
1364
1366
|
# transition all - [:idling, :first_gear] => :idling, :on => :ignite # Transitions every state but :idling and :first_gear to :idling
|
1365
1367
|
# transition nil => :idling, :on => :ignite # Transitions to :idling from the nil state
|
1366
1368
|
# transition :parked => :idling, :on => :ignite # Transitions to :idling if :parked
|
1367
1369
|
# transition [:parked, :stalled] => :idling, :on => :ignite # Transitions to :idling if :parked or :stalled
|
1368
|
-
#
|
1370
|
+
#
|
1369
1371
|
# transition :parked => same, :on => :park # Loops :parked back to :parked
|
1370
1372
|
# transition [:parked, :stalled] => same, :on => [:park, :stall] # Loops either :parked or :stalled back to the same state on the park and stall events
|
1371
1373
|
# transition all - :parked => same, :on => :noop # Loops every state but :parked back to the same state
|
1372
|
-
#
|
1374
|
+
#
|
1373
1375
|
# # Transitions to :idling if :parked, :first_gear if :idling, or :second_gear if :first_gear
|
1374
1376
|
# transition :parked => :idling, :idling => :first_gear, :first_gear => :second_gear, :on => :shift_up
|
1375
|
-
#
|
1377
|
+
#
|
1376
1378
|
# == Verbose transitions
|
1377
|
-
#
|
1379
|
+
#
|
1378
1380
|
# Transitions can also be defined use an explicit set of configuration
|
1379
1381
|
# options:
|
1380
1382
|
# * <tt>:from</tt> - A state or array of states that can be transitioned from.
|
@@ -1383,24 +1385,24 @@ module StateMachines
|
|
1383
1385
|
# then the transition will simply loop back (i.e. the state will not change).
|
1384
1386
|
# * <tt>:except_from</tt> - A state or array of states that *cannot* be
|
1385
1387
|
# transitioned from.
|
1386
|
-
#
|
1388
|
+
#
|
1387
1389
|
# These options must be used when defining transitions within the context
|
1388
1390
|
# of a state.
|
1389
|
-
#
|
1391
|
+
#
|
1390
1392
|
# Examples:
|
1391
|
-
#
|
1393
|
+
#
|
1392
1394
|
# transition :to => nil, :on => :park
|
1393
1395
|
# transition :to => :idling, :on => :ignite
|
1394
1396
|
# transition :except_from => [:idling, :first_gear], :to => :idling, :on => :ignite
|
1395
1397
|
# transition :from => nil, :to => :idling, :on => :ignite
|
1396
1398
|
# transition :from => [:parked, :stalled], :to => :idling, :on => :ignite
|
1397
|
-
#
|
1399
|
+
#
|
1398
1400
|
# == Conditions
|
1399
|
-
#
|
1401
|
+
#
|
1400
1402
|
# In addition to the state requirements for each transition, a condition
|
1401
1403
|
# can also be defined to help determine whether that transition is
|
1402
1404
|
# available. These options will work on both the normal and verbose syntax.
|
1403
|
-
#
|
1405
|
+
#
|
1404
1406
|
# Configuration options:
|
1405
1407
|
# * <tt>:if</tt> - A method, proc or string to call to determine if the
|
1406
1408
|
# transition should occur (e.g. :if => :moving?, or :if => lambda {|vehicle| vehicle.speed > 60}).
|
@@ -1408,18 +1410,18 @@ module StateMachines
|
|
1408
1410
|
# * <tt>:unless</tt> - A method, proc or string to call to determine if the
|
1409
1411
|
# transition should not occur (e.g. :unless => :stopped?, or :unless => lambda {|vehicle| vehicle.speed <= 60}).
|
1410
1412
|
# The condition should return or evaluate to true or false.
|
1411
|
-
#
|
1413
|
+
#
|
1412
1414
|
# Examples:
|
1413
|
-
#
|
1415
|
+
#
|
1414
1416
|
# transition :parked => :idling, :on => :ignite, :if => :moving?
|
1415
1417
|
# transition :parked => :idling, :on => :ignite, :unless => :stopped?
|
1416
1418
|
# transition :idling => :first_gear, :first_gear => :second_gear, :on => :shift_up, :if => :seatbelt_on?
|
1417
|
-
#
|
1419
|
+
#
|
1418
1420
|
# transition :from => :parked, :to => :idling, :on => ignite, :if => :moving?
|
1419
1421
|
# transition :from => :parked, :to => :idling, :on => ignite, :unless => :stopped?
|
1420
|
-
#
|
1422
|
+
#
|
1421
1423
|
# == Order of operations
|
1422
|
-
#
|
1424
|
+
#
|
1423
1425
|
# Transitions are evaluated in the order in which they're defined. As a
|
1424
1426
|
# result, if more than one transition applies to a given object, then the
|
1425
1427
|
# first transition that matches will be performed.
|
@@ -1435,12 +1437,12 @@ module StateMachines
|
|
1435
1437
|
|
1436
1438
|
# Creates a callback that will be invoked *before* a transition is
|
1437
1439
|
# performed so long as the given requirements match the transition.
|
1438
|
-
#
|
1440
|
+
#
|
1439
1441
|
# == The callback
|
1440
|
-
#
|
1442
|
+
#
|
1441
1443
|
# Callbacks must be defined as either an argument, in the :do option, or
|
1442
1444
|
# as a block. For example,
|
1443
|
-
#
|
1445
|
+
#
|
1444
1446
|
# class Vehicle
|
1445
1447
|
# state_machine do
|
1446
1448
|
# before_transition :set_alarm
|
@@ -1452,13 +1454,13 @@ module StateMachines
|
|
1452
1454
|
# ...
|
1453
1455
|
# end
|
1454
1456
|
# end
|
1455
|
-
#
|
1457
|
+
#
|
1456
1458
|
# Notice that the first three callbacks are the same in terms of how the
|
1457
1459
|
# methods to invoke are defined. However, using the <tt>:do</tt> can
|
1458
1460
|
# provide for a more fluid DSL.
|
1459
|
-
#
|
1461
|
+
#
|
1460
1462
|
# In addition, multiple callbacks can be defined like so:
|
1461
|
-
#
|
1463
|
+
#
|
1462
1464
|
# class Vehicle
|
1463
1465
|
# state_machine do
|
1464
1466
|
# before_transition :set_alarm, :lock_doors, all => :parked
|
@@ -1468,54 +1470,54 @@ module StateMachines
|
|
1468
1470
|
# end
|
1469
1471
|
# end
|
1470
1472
|
# end
|
1471
|
-
#
|
1473
|
+
#
|
1472
1474
|
# Notice that the different ways of configuring methods can be mixed.
|
1473
|
-
#
|
1475
|
+
#
|
1474
1476
|
# == State requirements
|
1475
|
-
#
|
1477
|
+
#
|
1476
1478
|
# Callbacks can require that the machine be transitioning from and to
|
1477
1479
|
# specific states. These requirements use a Hash syntax to map beginning
|
1478
1480
|
# states to ending states. For example,
|
1479
|
-
#
|
1481
|
+
#
|
1480
1482
|
# before_transition :parked => :idling, :idling => :first_gear, :do => :set_alarm
|
1481
|
-
#
|
1483
|
+
#
|
1482
1484
|
# In this case, the +set_alarm+ callback will only be called if the machine
|
1483
1485
|
# is transitioning from +parked+ to +idling+ or from +idling+ to +parked+.
|
1484
|
-
#
|
1486
|
+
#
|
1485
1487
|
# To help define state requirements, a set of helpers are available for
|
1486
1488
|
# slightly more complex matching:
|
1487
1489
|
# * <tt>all</tt> - Matches every state/event in the machine
|
1488
1490
|
# * <tt>all - [:parked, :idling, ...]</tt> - Matches every state/event except those specified
|
1489
1491
|
# * <tt>any</tt> - An alias for +all+ (matches every state/event in the machine)
|
1490
1492
|
# * <tt>same</tt> - Matches the same state being transitioned from
|
1491
|
-
#
|
1493
|
+
#
|
1492
1494
|
# See StateMachines::MatcherHelpers for more information.
|
1493
|
-
#
|
1495
|
+
#
|
1494
1496
|
# Examples:
|
1495
|
-
#
|
1497
|
+
#
|
1496
1498
|
# before_transition :parked => [:idling, :first_gear], :do => ... # Matches from parked to idling or first_gear
|
1497
1499
|
# before_transition all - [:parked, :idling] => :idling, :do => ... # Matches from every state except parked and idling to idling
|
1498
1500
|
# before_transition all => :parked, :do => ... # Matches all states to parked
|
1499
1501
|
# before_transition any => same, :do => ... # Matches every loopback
|
1500
|
-
#
|
1502
|
+
#
|
1501
1503
|
# == Event requirements
|
1502
|
-
#
|
1504
|
+
#
|
1503
1505
|
# In addition to state requirements, an event requirement can be defined so
|
1504
1506
|
# that the callback is only invoked on specific events using the +on+
|
1505
1507
|
# option. This can also use the same matcher helpers as the state
|
1506
1508
|
# requirements.
|
1507
|
-
#
|
1509
|
+
#
|
1508
1510
|
# Examples:
|
1509
|
-
#
|
1511
|
+
#
|
1510
1512
|
# before_transition :on => :ignite, :do => ... # Matches only on ignite
|
1511
1513
|
# before_transition :on => all - :ignite, :do => ... # Matches on every event except ignite
|
1512
1514
|
# before_transition :parked => :idling, :on => :ignite, :do => ... # Matches from parked to idling on ignite
|
1513
|
-
#
|
1515
|
+
#
|
1514
1516
|
# == Verbose Requirements
|
1515
|
-
#
|
1517
|
+
#
|
1516
1518
|
# Requirements can also be defined using verbose options rather than the
|
1517
1519
|
# implicit Hash syntax and helper methods described above.
|
1518
|
-
#
|
1520
|
+
#
|
1519
1521
|
# Configuration options:
|
1520
1522
|
# * <tt>:from</tt> - One or more states being transitioned from. If none
|
1521
1523
|
# are specified, then all states will match.
|
@@ -1526,116 +1528,116 @@ module StateMachines
|
|
1526
1528
|
# * <tt>:except_from</tt> - One or more states *not* being transitioned from
|
1527
1529
|
# * <tt>:except_to</tt> - One more states *not* being transitioned to
|
1528
1530
|
# * <tt>:except_on</tt> - One or more events that *did not* fire the transition
|
1529
|
-
#
|
1531
|
+
#
|
1530
1532
|
# Examples:
|
1531
|
-
#
|
1533
|
+
#
|
1532
1534
|
# before_transition :from => :ignite, :to => :idling, :on => :park, :do => ...
|
1533
1535
|
# before_transition :except_from => :ignite, :except_to => :idling, :except_on => :park, :do => ...
|
1534
|
-
#
|
1536
|
+
#
|
1535
1537
|
# == Conditions
|
1536
|
-
#
|
1538
|
+
#
|
1537
1539
|
# In addition to the state/event requirements, a condition can also be
|
1538
1540
|
# defined to help determine whether the callback should be invoked.
|
1539
|
-
#
|
1541
|
+
#
|
1540
1542
|
# Configuration options:
|
1541
1543
|
# * <tt>:if</tt> - A method, proc or string to call to determine if the
|
1542
1544
|
# callback should occur (e.g. :if => :allow_callbacks, or
|
1543
1545
|
# :if => lambda {|user| user.signup_step > 2}). The method, proc or string
|
1544
|
-
# should return or evaluate to a true or false value.
|
1546
|
+
# should return or evaluate to a true or false value.
|
1545
1547
|
# * <tt>:unless</tt> - A method, proc or string to call to determine if the
|
1546
1548
|
# callback should not occur (e.g. :unless => :skip_callbacks, or
|
1547
1549
|
# :unless => lambda {|user| user.signup_step <= 2}). The method, proc or
|
1548
|
-
# string should return or evaluate to a true or false value.
|
1549
|
-
#
|
1550
|
+
# string should return or evaluate to a true or false value.
|
1551
|
+
#
|
1550
1552
|
# Examples:
|
1551
|
-
#
|
1553
|
+
#
|
1552
1554
|
# before_transition :parked => :idling, :if => :moving?, :do => ...
|
1553
1555
|
# before_transition :on => :ignite, :unless => :seatbelt_on?, :do => ...
|
1554
|
-
#
|
1556
|
+
#
|
1555
1557
|
# == Accessing the transition
|
1556
|
-
#
|
1558
|
+
#
|
1557
1559
|
# In addition to passing the object being transitioned, the actual
|
1558
1560
|
# transition describing the context (e.g. event, from, to) can be accessed
|
1559
1561
|
# as well. This additional argument is only passed if the callback allows
|
1560
1562
|
# for it.
|
1561
|
-
#
|
1563
|
+
#
|
1562
1564
|
# For example,
|
1563
|
-
#
|
1565
|
+
#
|
1564
1566
|
# class Vehicle
|
1565
1567
|
# # Only specifies one parameter (the object being transitioned)
|
1566
1568
|
# before_transition all => :parked do |vehicle|
|
1567
1569
|
# vehicle.set_alarm
|
1568
1570
|
# end
|
1569
|
-
#
|
1571
|
+
#
|
1570
1572
|
# # Specifies 2 parameters (object being transitioned and actual transition)
|
1571
1573
|
# before_transition all => :parked do |vehicle, transition|
|
1572
1574
|
# vehicle.set_alarm(transition)
|
1573
1575
|
# end
|
1574
1576
|
# end
|
1575
|
-
#
|
1577
|
+
#
|
1576
1578
|
# *Note* that the object in the callback will only be passed in as an
|
1577
1579
|
# argument if callbacks are configured to *not* be bound to the object
|
1578
1580
|
# involved. This is the default and may change on a per-integration basis.
|
1579
|
-
#
|
1581
|
+
#
|
1580
1582
|
# See StateMachines::Transition for more information about the
|
1581
1583
|
# attributes available on the transition.
|
1582
|
-
#
|
1584
|
+
#
|
1583
1585
|
# == Usage with delegates
|
1584
|
-
#
|
1586
|
+
#
|
1585
1587
|
# As noted above, state_machine uses the callback method's argument list
|
1586
1588
|
# arity to determine whether to include the transition in the method call.
|
1587
1589
|
# If you're using delegates, such as those defined in ActiveSupport or
|
1588
1590
|
# Forwardable, the actual arity of the delegated method gets masked. This
|
1589
1591
|
# means that callbacks which reference delegates will always get passed the
|
1590
1592
|
# transition as an argument. For example:
|
1591
|
-
#
|
1593
|
+
#
|
1592
1594
|
# class Vehicle
|
1593
1595
|
# extend Forwardable
|
1594
1596
|
# delegate :refresh => :dashboard
|
1595
|
-
#
|
1597
|
+
#
|
1596
1598
|
# state_machine do
|
1597
1599
|
# before_transition :refresh
|
1598
1600
|
# ...
|
1599
1601
|
# end
|
1600
|
-
#
|
1602
|
+
#
|
1601
1603
|
# def dashboard
|
1602
1604
|
# @dashboard ||= Dashboard.new
|
1603
1605
|
# end
|
1604
1606
|
# end
|
1605
|
-
#
|
1607
|
+
#
|
1606
1608
|
# class Dashboard
|
1607
1609
|
# def refresh(transition)
|
1608
1610
|
# # ...
|
1609
1611
|
# end
|
1610
1612
|
# end
|
1611
|
-
#
|
1613
|
+
#
|
1612
1614
|
# In the above example, <tt>Dashboard#refresh</tt> *must* defined a
|
1613
1615
|
# +transition+ argument. Otherwise, an +ArgumentError+ exception will get
|
1614
1616
|
# raised. The only way around this is to avoid the use of delegates and
|
1615
1617
|
# manually define the delegate method so that the correct arity is used.
|
1616
|
-
#
|
1618
|
+
#
|
1617
1619
|
# == Examples
|
1618
|
-
#
|
1620
|
+
#
|
1619
1621
|
# Below is an example of a class with one state machine and various types
|
1620
1622
|
# of +before+ transitions defined for it:
|
1621
|
-
#
|
1623
|
+
#
|
1622
1624
|
# class Vehicle
|
1623
1625
|
# state_machine do
|
1624
1626
|
# # Before all transitions
|
1625
1627
|
# before_transition :update_dashboard
|
1626
|
-
#
|
1628
|
+
#
|
1627
1629
|
# # Before specific transition:
|
1628
1630
|
# before_transition [:first_gear, :idling] => :parked, :on => :park, :do => :take_off_seatbelt
|
1629
|
-
#
|
1631
|
+
#
|
1630
1632
|
# # With conditional callback:
|
1631
1633
|
# before_transition all => :parked, :do => :take_off_seatbelt, :if => :seatbelt_on?
|
1632
|
-
#
|
1634
|
+
#
|
1633
1635
|
# # Using helpers:
|
1634
1636
|
# before_transition all - :stalled => same, :on => any - :crash, :do => :update_dashboard
|
1635
1637
|
# ...
|
1636
1638
|
# end
|
1637
1639
|
# end
|
1638
|
-
#
|
1640
|
+
#
|
1639
1641
|
# As can be seen, any number of transitions can be created using various
|
1640
1642
|
# combinations of configuration options.
|
1641
1643
|
def before_transition(*args, &block)
|
@@ -1646,7 +1648,7 @@ module StateMachines
|
|
1646
1648
|
|
1647
1649
|
# Creates a callback that will be invoked *after* a transition is
|
1648
1650
|
# performed so long as the given requirements match the transition.
|
1649
|
-
#
|
1651
|
+
#
|
1650
1652
|
# See +before_transition+ for a description of the possible configurations
|
1651
1653
|
# for defining callbacks.
|
1652
1654
|
def after_transition(*args, &block)
|
@@ -1657,32 +1659,32 @@ module StateMachines
|
|
1657
1659
|
|
1658
1660
|
# Creates a callback that will be invoked *around* a transition so long as
|
1659
1661
|
# the given requirements match the transition.
|
1660
|
-
#
|
1662
|
+
#
|
1661
1663
|
# == The callback
|
1662
|
-
#
|
1664
|
+
#
|
1663
1665
|
# Around callbacks wrap transitions, executing code both before and after.
|
1664
1666
|
# These callbacks are defined in the exact same manner as before / after
|
1665
1667
|
# callbacks with the exception that the transition must be yielded to in
|
1666
1668
|
# order to finish running it.
|
1667
|
-
#
|
1669
|
+
#
|
1668
1670
|
# If defining +around+ callbacks using blocks, you must yield within the
|
1669
1671
|
# transition by directly calling the block (since yielding is not allowed
|
1670
1672
|
# within blocks).
|
1671
|
-
#
|
1673
|
+
#
|
1672
1674
|
# For example,
|
1673
|
-
#
|
1675
|
+
#
|
1674
1676
|
# class Vehicle
|
1675
1677
|
# state_machine do
|
1676
1678
|
# around_transition do |block|
|
1677
1679
|
# Benchmark.measure { block.call }
|
1678
1680
|
# end
|
1679
|
-
#
|
1681
|
+
#
|
1680
1682
|
# around_transition do |vehicle, block|
|
1681
1683
|
# logger.info "vehicle was #{state}..."
|
1682
1684
|
# block.call
|
1683
1685
|
# logger.info "...and is now #{state}"
|
1684
1686
|
# end
|
1685
|
-
#
|
1687
|
+
#
|
1686
1688
|
# around_transition do |vehicle, transition, block|
|
1687
1689
|
# logger.info "before #{transition.event}: #{vehicle.state}"
|
1688
1690
|
# block.call
|
@@ -1690,24 +1692,24 @@ module StateMachines
|
|
1690
1692
|
# end
|
1691
1693
|
# end
|
1692
1694
|
# end
|
1693
|
-
#
|
1695
|
+
#
|
1694
1696
|
# Notice that referencing the block is similar to doing so within an
|
1695
1697
|
# actual method definition in that it is always the last argument.
|
1696
|
-
#
|
1698
|
+
#
|
1697
1699
|
# On the other hand, if you're defining +around+ callbacks using method
|
1698
1700
|
# references, you can yield like normal:
|
1699
|
-
#
|
1701
|
+
#
|
1700
1702
|
# class Vehicle
|
1701
1703
|
# state_machine do
|
1702
1704
|
# around_transition :benchmark
|
1703
1705
|
# ...
|
1704
1706
|
# end
|
1705
|
-
#
|
1707
|
+
#
|
1706
1708
|
# def benchmark
|
1707
1709
|
# Benchmark.measure { yield }
|
1708
1710
|
# end
|
1709
1711
|
# end
|
1710
|
-
#
|
1712
|
+
#
|
1711
1713
|
# See +before_transition+ for a description of the possible configurations
|
1712
1714
|
# for defining callbacks.
|
1713
1715
|
def around_transition(*args, &block)
|
@@ -1718,29 +1720,29 @@ module StateMachines
|
|
1718
1720
|
|
1719
1721
|
# Creates a callback that will be invoked *after* a transition failures to
|
1720
1722
|
# be performed so long as the given requirements match the transition.
|
1721
|
-
#
|
1723
|
+
#
|
1722
1724
|
# See +before_transition+ for a description of the possible configurations
|
1723
1725
|
# for defining callbacks. *Note* however that you cannot define the state
|
1724
1726
|
# requirements in these callbacks. You may only define event requirements.
|
1725
|
-
#
|
1727
|
+
#
|
1726
1728
|
# = The callback
|
1727
|
-
#
|
1729
|
+
#
|
1728
1730
|
# Failure callbacks get invoked whenever an event fails to execute. This
|
1729
1731
|
# can happen when no transition is available, a +before+ callback halts
|
1730
1732
|
# execution, or the action associated with this machine fails to succeed.
|
1731
1733
|
# In any of these cases, any failure callback that matches the attempted
|
1732
1734
|
# transition will be run.
|
1733
|
-
#
|
1735
|
+
#
|
1734
1736
|
# For example,
|
1735
|
-
#
|
1737
|
+
#
|
1736
1738
|
# class Vehicle
|
1737
1739
|
# state_machine do
|
1738
1740
|
# after_failure do |vehicle, transition|
|
1739
1741
|
# logger.error "vehicle #{vehicle} failed to transition on #{transition.event}"
|
1740
1742
|
# end
|
1741
|
-
#
|
1743
|
+
#
|
1742
1744
|
# after_failure :on => :ignite, :do => :log_ignition_failure
|
1743
|
-
#
|
1745
|
+
#
|
1744
1746
|
# ...
|
1745
1747
|
# end
|
1746
1748
|
# end
|
@@ -1756,7 +1758,7 @@ module StateMachines
|
|
1756
1758
|
# the given object. These paths can reveal all of the possible states and
|
1757
1759
|
# events that can be encountered in the object's state machine based on the
|
1758
1760
|
# object's current state.
|
1759
|
-
#
|
1761
|
+
#
|
1760
1762
|
# Configuration options:
|
1761
1763
|
# * +from+ - The initial state to start all paths from. By default, this
|
1762
1764
|
# is the object's current state.
|
@@ -1767,31 +1769,31 @@ module StateMachines
|
|
1767
1769
|
# state (if specified) is reached. If this is enabled, then paths can
|
1768
1770
|
# continue even after reaching the target state; they will stop when
|
1769
1771
|
# reaching the target state a second time.
|
1770
|
-
#
|
1772
|
+
#
|
1771
1773
|
# *Note* that the object is never modified when the list of paths is
|
1772
1774
|
# generated.
|
1773
|
-
#
|
1775
|
+
#
|
1774
1776
|
# == Examples
|
1775
|
-
#
|
1777
|
+
#
|
1776
1778
|
# class Vehicle
|
1777
1779
|
# state_machine :initial => :parked do
|
1778
1780
|
# event :ignite do
|
1779
1781
|
# transition :parked => :idling
|
1780
1782
|
# end
|
1781
|
-
#
|
1783
|
+
#
|
1782
1784
|
# event :shift_up do
|
1783
1785
|
# transition :idling => :first_gear, :first_gear => :second_gear
|
1784
1786
|
# end
|
1785
|
-
#
|
1787
|
+
#
|
1786
1788
|
# event :shift_down do
|
1787
1789
|
# transition :second_gear => :first_gear, :first_gear => :idling
|
1788
1790
|
# end
|
1789
1791
|
# end
|
1790
1792
|
# end
|
1791
|
-
#
|
1793
|
+
#
|
1792
1794
|
# vehicle = Vehicle.new # => #<Vehicle:0xb7c27024 @state="parked">
|
1793
1795
|
# vehicle.state # => "parked"
|
1794
|
-
#
|
1796
|
+
#
|
1795
1797
|
# vehicle.state_paths
|
1796
1798
|
# # => [
|
1797
1799
|
# # [#<StateMachines::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>,
|
@@ -1799,26 +1801,26 @@ module StateMachines
|
|
1799
1801
|
# # #<StateMachines::Transition attribute=:state event=:shift_up from="first_gear" from_name=:first_gear to="second_gear" to_name=:second_gear>,
|
1800
1802
|
# # #<StateMachines::Transition attribute=:state event=:shift_down from="second_gear" from_name=:second_gear to="first_gear" to_name=:first_gear>,
|
1801
1803
|
# # #<StateMachines::Transition attribute=:state event=:shift_down from="first_gear" from_name=:first_gear to="idling" to_name=:idling>],
|
1802
|
-
# #
|
1804
|
+
# #
|
1803
1805
|
# # [#<StateMachines::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>,
|
1804
1806
|
# # #<StateMachines::Transition attribute=:state event=:shift_up from="idling" from_name=:idling to="first_gear" to_name=:first_gear>,
|
1805
1807
|
# # #<StateMachines::Transition attribute=:state event=:shift_down from="first_gear" from_name=:first_gear to="idling" to_name=:idling>]
|
1806
1808
|
# # ]
|
1807
|
-
#
|
1809
|
+
#
|
1808
1810
|
# vehicle.state_paths(:from => :parked, :to => :second_gear)
|
1809
1811
|
# # => [
|
1810
1812
|
# # [#<StateMachines::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>,
|
1811
1813
|
# # #<StateMachines::Transition attribute=:state event=:shift_up from="idling" from_name=:idling to="first_gear" to_name=:first_gear>,
|
1812
1814
|
# # #<StateMachines::Transition attribute=:state event=:shift_up from="first_gear" from_name=:first_gear to="second_gear" to_name=:second_gear>]
|
1813
1815
|
# # ]
|
1814
|
-
#
|
1816
|
+
#
|
1815
1817
|
# In addition to getting the possible paths that can be accessed, you can
|
1816
1818
|
# also get summary information about the states / events that can be
|
1817
1819
|
# accessed at some point along one of the paths. For example:
|
1818
|
-
#
|
1820
|
+
#
|
1819
1821
|
# # Get the list of states that can be accessed from the current state
|
1820
1822
|
# vehicle.state_paths.to_states # => [:idling, :first_gear, :second_gear]
|
1821
|
-
#
|
1823
|
+
#
|
1822
1824
|
# # Get the list of events that can be accessed from the current state
|
1823
1825
|
# vehicle.state_paths.events # => [:ignite, :shift_up, :shift_down]
|
1824
1826
|
def paths_for(object, requirements = {})
|
@@ -1826,7 +1828,7 @@ module StateMachines
|
|
1826
1828
|
end
|
1827
1829
|
|
1828
1830
|
# Marks the given object as invalid with the given message.
|
1829
|
-
#
|
1831
|
+
#
|
1830
1832
|
# By default, this is a no-op.
|
1831
1833
|
def invalidate(_object, _attribute, _message, _values = [])
|
1832
1834
|
end
|
@@ -1839,7 +1841,7 @@ module StateMachines
|
|
1839
1841
|
end
|
1840
1842
|
|
1841
1843
|
# Resets any errors previously added when invalidating the given object.
|
1842
|
-
#
|
1844
|
+
#
|
1843
1845
|
# By default, this is a no-op.
|
1844
1846
|
def reset(_object)
|
1845
1847
|
end
|
@@ -1859,7 +1861,7 @@ module StateMachines
|
|
1859
1861
|
end
|
1860
1862
|
|
1861
1863
|
# Runs a transaction, rolling back any changes if the yielded block fails.
|
1862
|
-
#
|
1864
|
+
#
|
1863
1865
|
# This is only applicable to integrations that involve databases. By
|
1864
1866
|
# default, this will not run any transactions since the changes aren't
|
1865
1867
|
# taking place within the context of a database.
|
@@ -1882,7 +1884,8 @@ module StateMachines
|
|
1882
1884
|
@action_hook_defined || !self_only && owner_class.state_machines.any? { |name, machine| machine.action == action && machine != self && machine.action_hook?(true) }
|
1883
1885
|
end
|
1884
1886
|
|
1885
|
-
|
1887
|
+
protected
|
1888
|
+
|
1886
1889
|
# Runs additional initialization hooks. By default, this is a no-op.
|
1887
1890
|
def after_initialize
|
1888
1891
|
end
|
@@ -2030,7 +2033,7 @@ module StateMachines
|
|
2030
2033
|
def #{action_hook}(*)
|
2031
2034
|
self.class.state_machines.transitions(self, #{action.inspect}).perform { super }
|
2032
2035
|
end
|
2033
|
-
|
2036
|
+
|
2034
2037
|
private #{action_hook.inspect} if #{private_action_hook}
|
2035
2038
|
end_eval
|
2036
2039
|
end
|
@@ -2052,7 +2055,7 @@ module StateMachines
|
|
2052
2055
|
def owner_class_ancestor_has_method?(scope, method)
|
2053
2056
|
return false unless owner_class_has_method?(scope, method)
|
2054
2057
|
|
2055
|
-
superclasses = owner_class.ancestors
|
2058
|
+
superclasses = owner_class.ancestors.select { |ancestor| ancestor.is_a?(Class) }[1..-1]
|
2056
2059
|
|
2057
2060
|
if scope == :class
|
2058
2061
|
current = owner_class.singleton_class
|
@@ -2116,7 +2119,7 @@ module StateMachines
|
|
2116
2119
|
[name, plural].map { |s| s.to_s }.uniq.each do |suffix|
|
2117
2120
|
method = "#{kind}_#{suffix}"
|
2118
2121
|
|
2119
|
-
if scope = send("create_#{kind}_scope", method)
|
2122
|
+
if (scope = send("create_#{kind}_scope", method))
|
2120
2123
|
# Converts state names to their corresponding values so that they
|
2121
2124
|
# can be looked up properly
|
2122
2125
|
define_helper(:class, method) do |machine, klass, *states|
|
@@ -2130,7 +2133,7 @@ module StateMachines
|
|
2130
2133
|
# Generates the results for the given scope based on one or more states to
|
2131
2134
|
# filter by
|
2132
2135
|
def run_scope(scope, machine, klass, states)
|
2133
|
-
values = states.flatten.map { |state| machine.states.fetch(state).value }
|
2136
|
+
values = states.flatten.compact.map { |state| machine.states.fetch(state).value }
|
2134
2137
|
scope.call(klass, values)
|
2135
2138
|
end
|
2136
2139
|
|
@@ -2198,11 +2201,11 @@ module StateMachines
|
|
2198
2201
|
new_states.map do |new_state|
|
2199
2202
|
# Check for other states that use a different class type for their name.
|
2200
2203
|
# This typically prevents string / symbol misuse.
|
2201
|
-
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 })
|
2202
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"
|
2203
2206
|
end
|
2204
2207
|
|
2205
|
-
unless state = states[new_state]
|
2208
|
+
unless (state = states[new_state])
|
2206
2209
|
states << state = State.new(self, new_state)
|
2207
2210
|
|
2208
2211
|
# Copy states over to sibling machines
|
@@ -2219,11 +2222,11 @@ module StateMachines
|
|
2219
2222
|
new_events.map do |new_event|
|
2220
2223
|
# Check for other states that use a different class type for their name.
|
2221
2224
|
# This typically prevents string / symbol misuse.
|
2222
|
-
if conflict = events.detect { |event| event.name.class != new_event.class }
|
2225
|
+
if (conflict = events.detect { |event| event.name.class != new_event.class })
|
2223
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"
|
2224
2227
|
end
|
2225
2228
|
|
2226
|
-
unless event = events[new_event]
|
2229
|
+
unless (event = events[new_event])
|
2227
2230
|
events << event = Event.new(self, new_event)
|
2228
2231
|
end
|
2229
2232
|
|