state_machines 0.100.4 → 0.200.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.
@@ -476,43 +476,6 @@ module StateMachines
476
476
  # Whether the machine will use transactions when firing events
477
477
  attr_reader :use_transactions
478
478
 
479
- # Creates a new state machine for the given attribute
480
-
481
- # Gets the initial state of the machine for the given object. If a dynamic
482
- # initial state was configured for this machine, then the object will be
483
- # passed into the lambda block to help determine the actual state.
484
- #
485
- # == Examples
486
- #
487
- # With a static initial state:
488
- #
489
- # class Vehicle
490
- # state_machine :initial => :parked do
491
- # ...
492
- # end
493
- # end
494
- #
495
- # vehicle = Vehicle.new
496
- # Vehicle.state_machine.initial_state(vehicle) # => #<StateMachines::State name=:parked value="parked" initial=true>
497
- #
498
- # With a dynamic initial state:
499
- #
500
- # class Vehicle
501
- # attr_accessor :force_idle
502
- #
503
- # state_machine :initial => lambda {|vehicle| vehicle.force_idle ? :idling : :parked} do
504
- # ...
505
- # end
506
- # end
507
- #
508
- # vehicle = Vehicle.new
509
- #
510
- # vehicle.force_idle = true
511
- # Vehicle.state_machine.initial_state(vehicle) # => #<StateMachines::State name=:idling value="idling" initial=false>
512
- #
513
- # vehicle.force_idle = false
514
- # Vehicle.state_machine.initial_state(vehicle) # => #<StateMachines::State name=:parked value="parked" initial=false>
515
-
516
479
  # Defines a new helper method in an instance or class scope with the given
517
480
  # name. If the method is already defined in the scope, then this will not
518
481
  # override it.
@@ -569,995 +532,6 @@ module StateMachines
569
532
  end
570
533
  end
571
534
 
572
- # Customizes the definition of one or more states in the machine.
573
- #
574
- # Configuration options:
575
- # * <tt>:value</tt> - The actual value to store when an object transitions
576
- # to the state. Default is the name (stringified).
577
- # * <tt>:cache</tt> - If a dynamic value (via a lambda block) is being used,
578
- # then setting this to true will cache the evaluated result
579
- # * <tt>:if</tt> - Determines whether an object's value matches the state
580
- # (e.g. :value => lambda {Time.now}, :if => lambda {|state| !state.nil?}).
581
- # By default, the configured value is matched.
582
- # * <tt>:human_name</tt> - The human-readable version of this state's name.
583
- # By default, this is either defined by the integration or stringifies the
584
- # name and converts underscores to spaces.
585
- #
586
- # == Customizing the stored value
587
- #
588
- # Whenever a state is automatically discovered in the state machine, its
589
- # default value is assumed to be the stringified version of the name. For
590
- # example,
591
- #
592
- # class Vehicle
593
- # state_machine :initial => :parked do
594
- # event :ignite do
595
- # transition :parked => :idling
596
- # end
597
- # end
598
- # end
599
- #
600
- # In the above state machine, there are two states automatically discovered:
601
- # :parked and :idling. These states, by default, will store their stringified
602
- # equivalents when an object moves into that state (e.g. "parked" / "idling").
603
- #
604
- # For legacy systems or when tying state machines into existing frameworks,
605
- # it's oftentimes necessary to need to store a different value for a state
606
- # than the default. In order to continue taking advantage of an expressive
607
- # state machine and helper methods, every defined state can be re-configured
608
- # with a custom stored value. For example,
609
- #
610
- # class Vehicle
611
- # state_machine :initial => :parked do
612
- # event :ignite do
613
- # transition :parked => :idling
614
- # end
615
- #
616
- # state :idling, :value => 'IDLING'
617
- # state :parked, :value => 'PARKED
618
- # end
619
- # end
620
- #
621
- # This is also useful if being used in association with a database and,
622
- # instead of storing the state name in a column, you want to store the
623
- # state's foreign key:
624
- #
625
- # class VehicleState < ActiveRecord::Base
626
- # end
627
- #
628
- # class Vehicle < ActiveRecord::Base
629
- # state_machine :attribute => :state_id, :initial => :parked do
630
- # event :ignite do
631
- # transition :parked => :idling
632
- # end
633
- #
634
- # states.each do |state|
635
- # self.state(state.name, :value => lambda { VehicleState.find_by_name(state.name.to_s).id }, :cache => true)
636
- # end
637
- # end
638
- # end
639
- #
640
- # In the above example, each known state is configured to store it's
641
- # associated database id in the +state_id+ attribute. Also, notice that a
642
- # lambda block is used to define the state's value. This is required in
643
- # situations (like testing) where the model is loaded without any existing
644
- # data (i.e. no VehicleState records available).
645
- #
646
- # One caveat to the above example is to keep performance in mind. To avoid
647
- # constant db hits for looking up the VehicleState ids, the value is cached
648
- # by specifying the <tt>:cache</tt> option. Alternatively, a custom
649
- # caching strategy can be used like so:
650
- #
651
- # class VehicleState < ActiveRecord::Base
652
- # cattr_accessor :cache_store
653
- # self.cache_store = ActiveSupport::Cache::MemoryStore.new
654
- #
655
- # def self.find_by_name(name)
656
- # cache_store.fetch(name) { find(:first, :conditions => {:name => name}) }
657
- # end
658
- # end
659
- #
660
- # === Dynamic values
661
- #
662
- # In addition to customizing states with other value types, lambda blocks
663
- # can also be specified to allow for a state's value to be determined
664
- # dynamically at runtime. For example,
665
- #
666
- # class Vehicle
667
- # state_machine :purchased_at, :initial => :available do
668
- # event :purchase do
669
- # transition all => :purchased
670
- # end
671
- #
672
- # event :restock do
673
- # transition all => :available
674
- # end
675
- #
676
- # state :available, :value => nil
677
- # state :purchased, :if => lambda {|value| !value.nil?}, :value => lambda {Time.now}
678
- # end
679
- # end
680
- #
681
- # In the above definition, the <tt>:purchased</tt> state is customized with
682
- # both a dynamic value *and* a value matcher.
683
- #
684
- # When an object transitions to the purchased state, the value's lambda
685
- # block will be called. This will get the current time and store it in the
686
- # object's +purchased_at+ attribute.
687
- #
688
- # *Note* that the custom matcher is very important here. Since there's no
689
- # way for the state machine to figure out an object's state when it's set to
690
- # a runtime value, it must be explicitly defined. If the <tt>:if</tt> option
691
- # were not configured for the state, then an ArgumentError exception would
692
- # be raised at runtime, indicating that the state machine could not figure
693
- # out what the current state of the object was.
694
- #
695
- # == Behaviors
696
- #
697
- # Behaviors define a series of methods to mixin with objects when the current
698
- # state matches the given one(s). This allows instance methods to behave
699
- # a specific way depending on what the value of the object's state is.
700
- #
701
- # For example,
702
- #
703
- # class Vehicle
704
- # attr_accessor :driver
705
- # attr_accessor :passenger
706
- #
707
- # state_machine :initial => :parked do
708
- # event :ignite do
709
- # transition :parked => :idling
710
- # end
711
- #
712
- # state :parked do
713
- # def speed
714
- # 0
715
- # end
716
- #
717
- # def rotate_driver
718
- # driver = self.driver
719
- # self.driver = passenger
720
- # self.passenger = driver
721
- # true
722
- # end
723
- # end
724
- #
725
- # state :idling, :first_gear do
726
- # def speed
727
- # 20
728
- # end
729
- #
730
- # def rotate_driver
731
- # self.state = 'parked'
732
- # rotate_driver
733
- # end
734
- # end
735
- #
736
- # other_states :backing_up
737
- # end
738
- # end
739
- #
740
- # In the above example, there are two dynamic behaviors defined for the
741
- # class:
742
- # * +speed+
743
- # * +rotate_driver+
744
- #
745
- # Each of these behaviors are instance methods on the Vehicle class. However,
746
- # which method actually gets invoked is based on the current state of the
747
- # object. Using the above class as the example:
748
- #
749
- # vehicle = Vehicle.new
750
- # vehicle.driver = 'John'
751
- # vehicle.passenger = 'Jane'
752
- #
753
- # # Behaviors in the "parked" state
754
- # vehicle.state # => "parked"
755
- # vehicle.speed # => 0
756
- # vehicle.rotate_driver # => true
757
- # vehicle.driver # => "Jane"
758
- # vehicle.passenger # => "John"
759
- #
760
- # vehicle.ignite # => true
761
- #
762
- # # Behaviors in the "idling" state
763
- # vehicle.state # => "idling"
764
- # vehicle.speed # => 20
765
- # vehicle.rotate_driver # => true
766
- # vehicle.driver # => "John"
767
- # vehicle.passenger # => "Jane"
768
- #
769
- # As can be seen, both the +speed+ and +rotate_driver+ instance method
770
- # implementations changed how they behave based on what the current state
771
- # of the vehicle was.
772
- #
773
- # === Invalid behaviors
774
- #
775
- # If a specific behavior has not been defined for a state, then a
776
- # NoMethodError exception will be raised, indicating that that method would
777
- # not normally exist for an object with that state.
778
- #
779
- # Using the example from before:
780
- #
781
- # vehicle = Vehicle.new
782
- # vehicle.state = 'backing_up'
783
- # vehicle.speed # => NoMethodError: undefined method 'speed' for #<Vehicle:0xb7d296ac> in state "backing_up"
784
- #
785
- # === Using matchers
786
- #
787
- # The +all+ / +any+ matchers can be used to easily define behaviors for a
788
- # group of states. Note, however, that you cannot use these matchers to
789
- # set configurations for states. Behaviors using these matchers can be
790
- # defined at any point in the state machine and will always get applied to
791
- # the proper states.
792
- #
793
- # For example:
794
- #
795
- # state_machine :initial => :parked do
796
- # ...
797
- #
798
- # state all - [:parked, :idling, :stalled] do
799
- # validates_presence_of :speed
800
- #
801
- # def speed
802
- # gear * 10
803
- # end
804
- # end
805
- # end
806
- #
807
- # == State-aware class methods
808
- #
809
- # In addition to defining scopes for instance methods that are state-aware,
810
- # the same can be done for certain types of class methods.
811
- #
812
- # Some libraries have support for class-level methods that only run certain
813
- # behaviors based on a conditions hash passed in. For example:
814
- #
815
- # class Vehicle < ActiveRecord::Base
816
- # state_machine do
817
- # ...
818
- # state :first_gear, :second_gear, :third_gear do
819
- # validates_presence_of :speed
820
- # validates_inclusion_of :speed, :in => 0..25, :if => :in_school_zone?
821
- # end
822
- # end
823
- # end
824
- #
825
- # In the above ActiveRecord model, two validations have been defined which
826
- # will *only* run when the Vehicle object is in one of the three states:
827
- # +first_gear+, +second_gear+, or +third_gear. Notice, also, that if/unless
828
- # conditions can continue to be used.
829
- #
830
- # This functionality is not library-specific and can work for any class-level
831
- # method that is defined like so:
832
- #
833
- # def validates_presence_of(attribute, options = {})
834
- # ...
835
- # end
836
- #
837
- # The minimum requirement is that the last argument in the method be an
838
- # options hash which contains at least <tt>:if</tt> condition support.
839
-
840
- # Defines one or more events for the machine and the transitions that can
841
- # be performed when those events are run.
842
- #
843
- # This method is also aliased as +on+ for improved compatibility with
844
- # using a domain-specific language.
845
- #
846
- # Configuration options:
847
- # * <tt>:human_name</tt> - The human-readable version of this event's name.
848
- # By default, this is either defined by the integration or stringifies the
849
- # name and converts underscores to spaces.
850
- #
851
- # == Instance methods
852
- #
853
- # The following instance methods are generated when a new event is defined
854
- # (the "park" event is used as an example):
855
- # * <tt>park(..., run_action = true)</tt> - Fires the "park" event,
856
- # transitioning from the current state to the next valid state. If the
857
- # last argument is a boolean, it will control whether the machine's action
858
- # gets run.
859
- # * <tt>park!(..., run_action = true)</tt> - Fires the "park" event,
860
- # transitioning from the current state to the next valid state. If the
861
- # transition fails, then a StateMachines::InvalidTransition error will be
862
- # raised. If the last argument is a boolean, it will control whether the
863
- # machine's action gets run.
864
- # * <tt>can_park?(requirements = {})</tt> - Checks whether the "park" event
865
- # can be fired given the current state of the object. This will *not* run
866
- # validations or callbacks in ORM integrations. It will only determine if
867
- # the state machine defines a valid transition for the event. To check
868
- # whether an event can fire *and* passes validations, use event attributes
869
- # (e.g. state_event) as described in the "Events" documentation of each
870
- # ORM integration.
871
- # * <tt>park_transition(requirements = {})</tt> - Gets the next transition
872
- # that would be performed if the "park" event were to be fired now on the
873
- # object or nil if no transitions can be performed. Like <tt>can_park?</tt>
874
- # this will also *not* run validations or callbacks. It will only
875
- # determine if the state machine defines a valid transition for the event.
876
- #
877
- # With a namespace of "car", the above names map to the following methods:
878
- # * <tt>can_park_car?</tt>
879
- # * <tt>park_car_transition</tt>
880
- # * <tt>park_car</tt>
881
- # * <tt>park_car!</tt>
882
- #
883
- # The <tt>can_park?</tt> and <tt>park_transition</tt> helpers both take an
884
- # optional set of requirements for determining what transitions are available
885
- # for the current object. These requirements include:
886
- # * <tt>:from</tt> - One or more states to transition from. If none are
887
- # specified, then this will be the object's current state.
888
- # * <tt>:to</tt> - One or more states to transition to. If none are
889
- # specified, then this will match any to state.
890
- # * <tt>:guard</tt> - Whether to guard transitions with the if/unless
891
- # conditionals defined for each one. Default is true.
892
- #
893
- # == Defining transitions
894
- #
895
- # +event+ requires a block which allows you to define the possible
896
- # transitions that can happen as a result of that event. For example,
897
- #
898
- # event :park, :stop do
899
- # transition :idling => :parked
900
- # end
901
- #
902
- # event :first_gear do
903
- # transition :parked => :first_gear, :if => :seatbelt_on?
904
- # transition :parked => same # Allow to loopback if seatbelt is off
905
- # end
906
- #
907
- # See StateMachines::Event#transition for more information on
908
- # the possible options that can be passed in.
909
- #
910
- # *Note* that this block is executed within the context of the actual event
911
- # object. As a result, you will not be able to reference any class methods
912
- # on the model without referencing the class itself. For example,
913
- #
914
- # class Vehicle
915
- # def self.safe_states
916
- # [:parked, :idling, :stalled]
917
- # end
918
- #
919
- # state_machine do
920
- # event :park do
921
- # transition Vehicle.safe_states => :parked
922
- # end
923
- # end
924
- # end
925
- #
926
- # == Overriding the event method
927
- #
928
- # By default, this will define an instance method (with the same name as the
929
- # event) that will fire the next possible transition for that. Although the
930
- # +before_transition+, +after_transition+, and +around_transition+ hooks
931
- # allow you to define behavior that gets executed as a result of the event's
932
- # transition, you can also override the event method in order to have a
933
- # little more fine-grained control.
934
- #
935
- # For example:
936
- #
937
- # class Vehicle
938
- # state_machine do
939
- # event :park do
940
- # ...
941
- # end
942
- # end
943
- #
944
- # def park(*)
945
- # take_deep_breath # Executes before the transition (and before_transition hooks) even if no transition is possible
946
- # if result = super # Runs the transition and all before/after/around hooks
947
- # applaud # Executes after the transition (and after_transition hooks)
948
- # end
949
- # result
950
- # end
951
- # end
952
- #
953
- # There are a few important things to note here. First, the method
954
- # signature is defined with an unlimited argument list in order to allow
955
- # callers to continue passing arguments that are expected by state_machine.
956
- # For example, it will still allow calls to +park+ with a single parameter
957
- # for skipping the configured action.
958
- #
959
- # Second, the overridden event method must call +super+ in order to run the
960
- # logic for running the next possible transition. In order to remain
961
- # consistent with other events, the result of +super+ is returned.
962
- #
963
- # Third, any behavior defined in this method will *not* get executed if
964
- # you're taking advantage of attribute-based event transitions. For example:
965
- #
966
- # vehicle = Vehicle.new
967
- # vehicle.state_event = 'park'
968
- # vehicle.save
969
- #
970
- # In this case, the +park+ event will run the before/after/around transition
971
- # hooks and transition the state, but the behavior defined in the overriden
972
- # +park+ method will *not* be executed.
973
- #
974
- # == Defining additional arguments
975
- #
976
- # Additional arguments can be passed into events and accessed by transition
977
- # hooks like so:
978
- #
979
- # class Vehicle
980
- # state_machine do
981
- # after_transition :on => :park do |vehicle, transition|
982
- # kind = *transition.args # :parallel
983
- # ...
984
- # end
985
- # after_transition :on => :park, :do => :take_deep_breath
986
- #
987
- # event :park do
988
- # ...
989
- # end
990
- #
991
- # def take_deep_breath(transition)
992
- # kind = *transition.args # :parallel
993
- # ...
994
- # end
995
- # end
996
- # end
997
- #
998
- # vehicle = Vehicle.new
999
- # vehicle.park(:parallel)
1000
- #
1001
- # *Remember* that if the last argument is a boolean, it will be used as the
1002
- # +run_action+ parameter to the event action. Using the +park+ action
1003
- # example from above, you can might call it like so:
1004
- #
1005
- # vehicle.park # => Uses default args and runs machine action
1006
- # vehicle.park(:parallel) # => Specifies the +kind+ argument and runs the machine action
1007
- # vehicle.park(:parallel, false) # => Specifies the +kind+ argument and *skips* the machine action
1008
- #
1009
- # If you decide to override the +park+ event method *and* define additional
1010
- # arguments, you can do so as shown below:
1011
- #
1012
- # class Vehicle
1013
- # state_machine do
1014
- # event :park do
1015
- # ...
1016
- # end
1017
- # end
1018
- #
1019
- # def park(kind = :parallel, *args)
1020
- # take_deep_breath if kind == :parallel
1021
- # super
1022
- # end
1023
- # end
1024
- #
1025
- # Note that +super+ is called instead of <tt>super(*args)</tt>. This allow
1026
- # the entire arguments list to be accessed by transition callbacks through
1027
- # StateMachines::Transition#args.
1028
- #
1029
- # === Using matchers
1030
- #
1031
- # The +all+ / +any+ matchers can be used to easily execute blocks for a
1032
- # group of events. Note, however, that you cannot use these matchers to
1033
- # set configurations for events. Blocks using these matchers can be
1034
- # defined at any point in the state machine and will always get applied to
1035
- # the proper events.
1036
- #
1037
- # For example:
1038
- #
1039
- # state_machine :initial => :parked do
1040
- # ...
1041
- #
1042
- # event all - [:crash] do
1043
- # transition :stalled => :parked
1044
- # end
1045
- # end
1046
- #
1047
- # == Example
1048
- #
1049
- # class Vehicle
1050
- # state_machine do
1051
- # # The park, stop, and halt events will all share the given transitions
1052
- # event :park, :stop, :halt do
1053
- # transition [:idling, :backing_up] => :parked
1054
- # end
1055
- #
1056
- # event :stop do
1057
- # transition :first_gear => :idling
1058
- # end
1059
- #
1060
- # event :ignite do
1061
- # transition :parked => :idling
1062
- # transition :idling => same # Allow ignite while still idling
1063
- # end
1064
- # end
1065
- # end
1066
-
1067
- # Creates a new transition that determines what to change the current state
1068
- # to when an event fires.
1069
- #
1070
- # == Defining transitions
1071
- #
1072
- # The options for a new transition uses the Hash syntax to map beginning
1073
- # states to ending states. For example,
1074
- #
1075
- # transition :parked => :idling, :idling => :first_gear, :on => :ignite
1076
- #
1077
- # In this case, when the +ignite+ event is fired, this transition will cause
1078
- # the state to be +idling+ if it's current state is +parked+ or +first_gear+
1079
- # if it's current state is +idling+.
1080
- #
1081
- # To help define these implicit transitions, a set of helpers are available
1082
- # for slightly more complex matching:
1083
- # * <tt>all</tt> - Matches every state in the machine
1084
- # * <tt>all - [:parked, :idling, ...]</tt> - Matches every state except those specified
1085
- # * <tt>any</tt> - An alias for +all+ (matches every state in the machine)
1086
- # * <tt>same</tt> - Matches the same state being transitioned from
1087
- #
1088
- # See StateMachines::MatcherHelpers for more information.
1089
- #
1090
- # Examples:
1091
- #
1092
- # transition all => nil, :on => :ignite # Transitions to nil regardless of the current state
1093
- # transition all => :idling, :on => :ignite # Transitions to :idling regardless of the current state
1094
- # transition all - [:idling, :first_gear] => :idling, :on => :ignite # Transitions every state but :idling and :first_gear to :idling
1095
- # transition nil => :idling, :on => :ignite # Transitions to :idling from the nil state
1096
- # transition :parked => :idling, :on => :ignite # Transitions to :idling if :parked
1097
- # transition [:parked, :stalled] => :idling, :on => :ignite # Transitions to :idling if :parked or :stalled
1098
- #
1099
- # transition :parked => same, :on => :park # Loops :parked back to :parked
1100
- # transition [:parked, :stalled] => same, :on => [:park, :stall] # Loops either :parked or :stalled back to the same state on the park and stall events
1101
- # transition all - :parked => same, :on => :noop # Loops every state but :parked back to the same state
1102
- #
1103
- # # Transitions to :idling if :parked, :first_gear if :idling, or :second_gear if :first_gear
1104
- # transition :parked => :idling, :idling => :first_gear, :first_gear => :second_gear, :on => :shift_up
1105
- #
1106
- # == Verbose transitions
1107
- #
1108
- # Transitions can also be defined use an explicit set of configuration
1109
- # options:
1110
- # * <tt>:from</tt> - A state or array of states that can be transitioned from.
1111
- # If not specified, then the transition can occur for *any* state.
1112
- # * <tt>:to</tt> - The state that's being transitioned to. If not specified,
1113
- # then the transition will simply loop back (i.e. the state will not change).
1114
- # * <tt>:except_from</tt> - A state or array of states that *cannot* be
1115
- # transitioned from.
1116
- #
1117
- # These options must be used when defining transitions within the context
1118
- # of a state.
1119
- #
1120
- # Examples:
1121
- #
1122
- # transition :to => nil, :on => :park
1123
- # transition :to => :idling, :on => :ignite
1124
- # transition :except_from => [:idling, :first_gear], :to => :idling, :on => :ignite
1125
- # transition :from => nil, :to => :idling, :on => :ignite
1126
- # transition :from => [:parked, :stalled], :to => :idling, :on => :ignite
1127
- #
1128
- # == Conditions
1129
- #
1130
- # In addition to the state requirements for each transition, a condition
1131
- # can also be defined to help determine whether that transition is
1132
- # available. These options will work on both the normal and verbose syntax.
1133
- #
1134
- # Configuration options:
1135
- # * <tt>:if</tt> - A method, proc or string to call to determine if the
1136
- # transition should occur (e.g. :if => :moving?, or :if => lambda {|vehicle| vehicle.speed > 60}).
1137
- # The condition should return or evaluate to true or false.
1138
- # * <tt>:unless</tt> - A method, proc or string to call to determine if the
1139
- # transition should not occur (e.g. :unless => :stopped?, or :unless => lambda {|vehicle| vehicle.speed <= 60}).
1140
- # The condition should return or evaluate to true or false.
1141
- #
1142
- # Examples:
1143
- #
1144
- # transition :parked => :idling, :on => :ignite, :if => :moving?
1145
- # transition :parked => :idling, :on => :ignite, :unless => :stopped?
1146
- # transition :idling => :first_gear, :first_gear => :second_gear, :on => :shift_up, :if => :seatbelt_on?
1147
- #
1148
- # transition :from => :parked, :to => :idling, :on => ignite, :if => :moving?
1149
- # transition :from => :parked, :to => :idling, :on => ignite, :unless => :stopped?
1150
- #
1151
- # == Order of operations
1152
- #
1153
- # Transitions are evaluated in the order in which they're defined. As a
1154
- # result, if more than one transition applies to a given object, then the
1155
- # first transition that matches will be performed.
1156
-
1157
- # Creates a callback that will be invoked *before* a transition is
1158
- # performed so long as the given requirements match the transition.
1159
- #
1160
- # == The callback
1161
- #
1162
- # Callbacks must be defined as either an argument, in the :do option, or
1163
- # as a block. For example,
1164
- #
1165
- # class Vehicle
1166
- # state_machine do
1167
- # before_transition :set_alarm
1168
- # before_transition :set_alarm, all => :parked
1169
- # before_transition all => :parked, :do => :set_alarm
1170
- # before_transition all => :parked do |vehicle, transition|
1171
- # vehicle.set_alarm
1172
- # end
1173
- # ...
1174
- # end
1175
- # end
1176
- #
1177
- # Notice that the first three callbacks are the same in terms of how the
1178
- # methods to invoke are defined. However, using the <tt>:do</tt> can
1179
- # provide for a more fluid DSL.
1180
- #
1181
- # In addition, multiple callbacks can be defined like so:
1182
- #
1183
- # class Vehicle
1184
- # state_machine do
1185
- # before_transition :set_alarm, :lock_doors, all => :parked
1186
- # before_transition all => :parked, :do => [:set_alarm, :lock_doors]
1187
- # before_transition :set_alarm do |vehicle, transition|
1188
- # vehicle.lock_doors
1189
- # end
1190
- # end
1191
- # end
1192
- #
1193
- # Notice that the different ways of configuring methods can be mixed.
1194
- #
1195
- # == State requirements
1196
- #
1197
- # Callbacks can require that the machine be transitioning from and to
1198
- # specific states. These requirements use a Hash syntax to map beginning
1199
- # states to ending states. For example,
1200
- #
1201
- # before_transition :parked => :idling, :idling => :first_gear, :do => :set_alarm
1202
- #
1203
- # In this case, the +set_alarm+ callback will only be called if the machine
1204
- # is transitioning from +parked+ to +idling+ or from +idling+ to +parked+.
1205
- #
1206
- # To help define state requirements, a set of helpers are available for
1207
- # slightly more complex matching:
1208
- # * <tt>all</tt> - Matches every state/event in the machine
1209
- # * <tt>all - [:parked, :idling, ...]</tt> - Matches every state/event except those specified
1210
- # * <tt>any</tt> - An alias for +all+ (matches every state/event in the machine)
1211
- # * <tt>same</tt> - Matches the same state being transitioned from
1212
- #
1213
- # See StateMachines::MatcherHelpers for more information.
1214
- #
1215
- # Examples:
1216
- #
1217
- # before_transition :parked => [:idling, :first_gear], :do => ... # Matches from parked to idling or first_gear
1218
- # before_transition all - [:parked, :idling] => :idling, :do => ... # Matches from every state except parked and idling to idling
1219
- # before_transition all => :parked, :do => ... # Matches all states to parked
1220
- # before_transition any => same, :do => ... # Matches every loopback
1221
- #
1222
- # == Event requirements
1223
- #
1224
- # In addition to state requirements, an event requirement can be defined so
1225
- # that the callback is only invoked on specific events using the +on+
1226
- # option. This can also use the same matcher helpers as the state
1227
- # requirements.
1228
- #
1229
- # Examples:
1230
- #
1231
- # before_transition :on => :ignite, :do => ... # Matches only on ignite
1232
- # before_transition :on => all - :ignite, :do => ... # Matches on every event except ignite
1233
- # before_transition :parked => :idling, :on => :ignite, :do => ... # Matches from parked to idling on ignite
1234
- #
1235
- # == Verbose Requirements
1236
- #
1237
- # Requirements can also be defined using verbose options rather than the
1238
- # implicit Hash syntax and helper methods described above.
1239
- #
1240
- # Configuration options:
1241
- # * <tt>:from</tt> - One or more states being transitioned from. If none
1242
- # are specified, then all states will match.
1243
- # * <tt>:to</tt> - One or more states being transitioned to. If none are
1244
- # specified, then all states will match.
1245
- # * <tt>:on</tt> - One or more events that fired the transition. If none
1246
- # are specified, then all events will match.
1247
- # * <tt>:except_from</tt> - One or more states *not* being transitioned from
1248
- # * <tt>:except_to</tt> - One more states *not* being transitioned to
1249
- # * <tt>:except_on</tt> - One or more events that *did not* fire the transition
1250
- #
1251
- # Examples:
1252
- #
1253
- # before_transition :from => :ignite, :to => :idling, :on => :park, :do => ...
1254
- # before_transition :except_from => :ignite, :except_to => :idling, :except_on => :park, :do => ...
1255
- #
1256
- # == Conditions
1257
- #
1258
- # In addition to the state/event requirements, a condition can also be
1259
- # defined to help determine whether the callback should be invoked.
1260
- #
1261
- # Configuration options:
1262
- # * <tt>:if</tt> - A method, proc or string to call to determine if the
1263
- # callback should occur (e.g. :if => :allow_callbacks, or
1264
- # :if => lambda {|user| user.signup_step > 2}). The method, proc or string
1265
- # should return or evaluate to a true or false value.
1266
- # * <tt>:unless</tt> - A method, proc or string to call to determine if the
1267
- # callback should not occur (e.g. :unless => :skip_callbacks, or
1268
- # :unless => lambda {|user| user.signup_step <= 2}). The method, proc or
1269
- # string should return or evaluate to a true or false value.
1270
- #
1271
- # Examples:
1272
- #
1273
- # before_transition :parked => :idling, :if => :moving?, :do => ...
1274
- # before_transition :on => :ignite, :unless => :seatbelt_on?, :do => ...
1275
- #
1276
- # == Accessing the transition
1277
- #
1278
- # In addition to passing the object being transitioned, the actual
1279
- # transition describing the context (e.g. event, from, to) can be accessed
1280
- # as well. This additional argument is only passed if the callback allows
1281
- # for it.
1282
- #
1283
- # For example,
1284
- #
1285
- # class Vehicle
1286
- # # Only specifies one parameter (the object being transitioned)
1287
- # before_transition all => :parked do |vehicle|
1288
- # vehicle.set_alarm
1289
- # end
1290
- #
1291
- # # Specifies 2 parameters (object being transitioned and actual transition)
1292
- # before_transition all => :parked do |vehicle, transition|
1293
- # vehicle.set_alarm(transition)
1294
- # end
1295
- # end
1296
- #
1297
- # *Note* that the object in the callback will only be passed in as an
1298
- # argument if callbacks are configured to *not* be bound to the object
1299
- # involved. This is the default and may change on a per-integration basis.
1300
- #
1301
- # See StateMachines::Transition for more information about the
1302
- # attributes available on the transition.
1303
- #
1304
- # == Usage with delegates
1305
- #
1306
- # As noted above, state_machine uses the callback method's argument list
1307
- # arity to determine whether to include the transition in the method call.
1308
- # If you're using delegates, such as those defined in ActiveSupport or
1309
- # Forwardable, the actual arity of the delegated method gets masked. This
1310
- # means that callbacks which reference delegates will always get passed the
1311
- # transition as an argument. For example:
1312
- #
1313
- # class Vehicle
1314
- # extend Forwardable
1315
- # delegate :refresh => :dashboard
1316
- #
1317
- # state_machine do
1318
- # before_transition :refresh
1319
- # ...
1320
- # end
1321
- #
1322
- # def dashboard
1323
- # @dashboard ||= Dashboard.new
1324
- # end
1325
- # end
1326
- #
1327
- # class Dashboard
1328
- # def refresh(transition)
1329
- # # ...
1330
- # end
1331
- # end
1332
- #
1333
- # In the above example, <tt>Dashboard#refresh</tt> *must* defined a
1334
- # +transition+ argument. Otherwise, an +ArgumentError+ exception will get
1335
- # raised. The only way around this is to avoid the use of delegates and
1336
- # manually define the delegate method so that the correct arity is used.
1337
- #
1338
- # == Examples
1339
- #
1340
- # Below is an example of a class with one state machine and various types
1341
- # of +before+ transitions defined for it:
1342
- #
1343
- # class Vehicle
1344
- # state_machine do
1345
- # # Before all transitions
1346
- # before_transition :update_dashboard
1347
- #
1348
- # # Before specific transition:
1349
- # before_transition [:first_gear, :idling] => :parked, :on => :park, :do => :take_off_seatbelt
1350
- #
1351
- # # With conditional callback:
1352
- # before_transition all => :parked, :do => :take_off_seatbelt, :if => :seatbelt_on?
1353
- #
1354
- # # Using helpers:
1355
- # before_transition all - :stalled => same, :on => any - :crash, :do => :update_dashboard
1356
- # ...
1357
- # end
1358
- # end
1359
- #
1360
- # As can be seen, any number of transitions can be created using various
1361
- # combinations of configuration options.
1362
- def before_transition(*args, **options, &)
1363
- # Extract legacy positional arguments and merge with keyword options
1364
- parsed_options = parse_callback_arguments(args, options)
1365
-
1366
- # Only validate callback-specific options, not state transition requirements
1367
- callback_options = parsed_options.slice(:do, :if, :unless, :bind_to_object, :terminator)
1368
- StateMachines::OptionsValidator.assert_valid_keys!(callback_options, :do, :if, :unless, :bind_to_object, :terminator)
1369
-
1370
- add_callback(:before, parsed_options, &)
1371
- end
1372
-
1373
- # Creates a callback that will be invoked *after* a transition is
1374
- # performed so long as the given requirements match the transition.
1375
- #
1376
- # See +before_transition+ for a description of the possible configurations
1377
- # for defining callbacks.
1378
- def after_transition(*args, **options, &)
1379
- # Extract legacy positional arguments and merge with keyword options
1380
- parsed_options = parse_callback_arguments(args, options)
1381
-
1382
- # Only validate callback-specific options, not state transition requirements
1383
- callback_options = parsed_options.slice(:do, :if, :unless, :bind_to_object, :terminator)
1384
- StateMachines::OptionsValidator.assert_valid_keys!(callback_options, :do, :if, :unless, :bind_to_object, :terminator)
1385
-
1386
- add_callback(:after, parsed_options, &)
1387
- end
1388
-
1389
- # Creates a callback that will be invoked *around* a transition so long as
1390
- # the given requirements match the transition.
1391
- #
1392
- # == The callback
1393
- #
1394
- # Around callbacks wrap transitions, executing code both before and after.
1395
- # These callbacks are defined in the exact same manner as before / after
1396
- # callbacks with the exception that the transition must be yielded to in
1397
- # order to finish running it.
1398
- #
1399
- # If defining +around+ callbacks using blocks, you must yield within the
1400
- # transition by directly calling the block (since yielding is not allowed
1401
- # within blocks).
1402
- #
1403
- # For example,
1404
- #
1405
- # class Vehicle
1406
- # state_machine do
1407
- # around_transition do |block|
1408
- # Benchmark.measure { block.call }
1409
- # end
1410
- #
1411
- # around_transition do |vehicle, block|
1412
- # logger.info "vehicle was #{state}..."
1413
- # block.call
1414
- # logger.info "...and is now #{state}"
1415
- # end
1416
- #
1417
- # around_transition do |vehicle, transition, block|
1418
- # logger.info "before #{transition.event}: #{vehicle.state}"
1419
- # block.call
1420
- # logger.info "after #{transition.event}: #{vehicle.state}"
1421
- # end
1422
- # end
1423
- # end
1424
- #
1425
- # Notice that referencing the block is similar to doing so within an
1426
- # actual method definition in that it is always the last argument.
1427
- #
1428
- # On the other hand, if you're defining +around+ callbacks using method
1429
- # references, you can yield like normal:
1430
- #
1431
- # class Vehicle
1432
- # state_machine do
1433
- # around_transition :benchmark
1434
- # ...
1435
- # end
1436
- #
1437
- # def benchmark
1438
- # Benchmark.measure { yield }
1439
- # end
1440
- # end
1441
- #
1442
- # See +before_transition+ for a description of the possible configurations
1443
- # for defining callbacks.
1444
- def around_transition(*args, **options, &)
1445
- # Extract legacy positional arguments and merge with keyword options
1446
- parsed_options = parse_callback_arguments(args, options)
1447
-
1448
- # Only validate callback-specific options, not state transition requirements
1449
- callback_options = parsed_options.slice(:do, :if, :unless, :bind_to_object, :terminator)
1450
- StateMachines::OptionsValidator.assert_valid_keys!(callback_options, :do, :if, :unless, :bind_to_object, :terminator)
1451
-
1452
- add_callback(:around, parsed_options, &)
1453
- end
1454
-
1455
- # Creates a callback that will be invoked *after* a transition failures to
1456
- # be performed so long as the given requirements match the transition.
1457
- #
1458
- # See +before_transition+ for a description of the possible configurations
1459
- # for defining callbacks. *Note* however that you cannot define the state
1460
- # requirements in these callbacks. You may only define event requirements.
1461
- #
1462
- # = The callback
1463
- #
1464
- # Failure callbacks get invoked whenever an event fails to execute. This
1465
- # can happen when no transition is available, a +before+ callback halts
1466
- # execution, or the action associated with this machine fails to succeed.
1467
- # In any of these cases, any failure callback that matches the attempted
1468
- # transition will be run.
1469
- #
1470
- # For example,
1471
- #
1472
- # class Vehicle
1473
- # state_machine do
1474
- # after_failure do |vehicle, transition|
1475
- # logger.error "vehicle #{vehicle} failed to transition on #{transition.event}"
1476
- # end
1477
- #
1478
- # after_failure :on => :ignite, :do => :log_ignition_failure
1479
- #
1480
- # ...
1481
- # end
1482
- # end
1483
- def after_failure(*args, **options, &)
1484
- # Extract legacy positional arguments and merge with keyword options
1485
- parsed_options = parse_callback_arguments(args, options)
1486
- StateMachines::OptionsValidator.assert_valid_keys!(parsed_options, :on, :do, :if, :unless)
1487
-
1488
- add_callback(:failure, parsed_options, &)
1489
- end
1490
-
1491
- # Generates a list of the possible transition sequences that can be run on
1492
- # the given object. These paths can reveal all of the possible states and
1493
- # events that can be encountered in the object's state machine based on the
1494
- # object's current state.
1495
- #
1496
- # Configuration options:
1497
- # * +from+ - The initial state to start all paths from. By default, this
1498
- # is the object's current state.
1499
- # * +to+ - The target state to end all paths on. By default, paths will
1500
- # end when they loop back to the first transition on the path.
1501
- # * +deep+ - Whether to allow the target state to be crossed more than once
1502
- # in a path. By default, paths will immediately stop when the target
1503
- # state (if specified) is reached. If this is enabled, then paths can
1504
- # continue even after reaching the target state; they will stop when
1505
- # reaching the target state a second time.
1506
- #
1507
- # *Note* that the object is never modified when the list of paths is
1508
- # generated.
1509
- #
1510
- # == Examples
1511
- #
1512
- # class Vehicle
1513
- # state_machine :initial => :parked do
1514
- # event :ignite do
1515
- # transition :parked => :idling
1516
- # end
1517
- #
1518
- # event :shift_up do
1519
- # transition :idling => :first_gear, :first_gear => :second_gear
1520
- # end
1521
- #
1522
- # event :shift_down do
1523
- # transition :second_gear => :first_gear, :first_gear => :idling
1524
- # end
1525
- # end
1526
- # end
1527
- #
1528
- # vehicle = Vehicle.new # => #<Vehicle:0xb7c27024 @state="parked">
1529
- # vehicle.state # => "parked"
1530
- #
1531
- # vehicle.state_paths
1532
- # # => [
1533
- # # [#<StateMachines::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>,
1534
- # # #<StateMachines::Transition attribute=:state event=:shift_up from="idling" from_name=:idling to="first_gear" to_name=:first_gear>,
1535
- # # #<StateMachines::Transition attribute=:state event=:shift_up from="first_gear" from_name=:first_gear to="second_gear" to_name=:second_gear>,
1536
- # # #<StateMachines::Transition attribute=:state event=:shift_down from="second_gear" from_name=:second_gear to="first_gear" to_name=:first_gear>,
1537
- # # #<StateMachines::Transition attribute=:state event=:shift_down from="first_gear" from_name=:first_gear to="idling" to_name=:idling>],
1538
- # #
1539
- # # [#<StateMachines::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>,
1540
- # # #<StateMachines::Transition attribute=:state event=:shift_up from="idling" from_name=:idling to="first_gear" to_name=:first_gear>,
1541
- # # #<StateMachines::Transition attribute=:state event=:shift_down from="first_gear" from_name=:first_gear to="idling" to_name=:idling>]
1542
- # # ]
1543
- #
1544
- # vehicle.state_paths(:from => :parked, :to => :second_gear)
1545
- # # => [
1546
- # # [#<StateMachines::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>,
1547
- # # #<StateMachines::Transition attribute=:state event=:shift_up from="idling" from_name=:idling to="first_gear" to_name=:first_gear>,
1548
- # # #<StateMachines::Transition attribute=:state event=:shift_up from="first_gear" from_name=:first_gear to="second_gear" to_name=:second_gear>]
1549
- # # ]
1550
- #
1551
- # In addition to getting the possible paths that can be accessed, you can
1552
- # also get summary information about the states / events that can be
1553
- # accessed at some point along one of the paths. For example:
1554
- #
1555
- # # Get the list of states that can be accessed from the current state
1556
- # vehicle.state_paths.to_states # => [:idling, :first_gear, :second_gear]
1557
- #
1558
- # # Get the list of events that can be accessed from the current state
1559
- # vehicle.state_paths.events # => [:ignite, :shift_up, :shift_down]
1560
-
1561
535
  # Marks the given object as invalid with the given message.
1562
536
  #
1563
537
  # By default, this is a no-op.
@@ -1621,11 +595,6 @@ module StateMachines
1621
595
  # Runs additional initialization hooks. By default, this is a no-op.
1622
596
  def after_initialize; end
1623
597
 
1624
- # Determines whether there's already a helper method defined within the
1625
- # given scope. This is true only if one of the owner's ancestors defines
1626
- # the method and is further along in the ancestor chain than this
1627
- # machine's helper module.
1628
-
1629
598
  # Always yields
1630
599
  def transaction(_object)
1631
600
  yield