Class: StateMachine::Guard

Inherits:
Object
  • Object
show all
Includes:
Assertions, EvalHelpers
Defined in:
lib/state_machine/guard.rb

Overview

Represents a set of requirements that must be met in order for a transition or callback to occur. Guards verify that the event, from state, and to state of the transition match, in addition to if/unless conditionals for an object’s state.

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from EvalHelpers

#evaluate_method

Methods included from Assertions

#assert_exclusive_keys, #assert_valid_keys

Constructor Details

#initialize(options = {}) ⇒ Guard

Creates a new guard



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/state_machine/guard.rb', line 37

def initialize(options = {}) #:nodoc:
  # Build conditionals
  @if_condition = options.delete(:if)
  @unless_condition = options.delete(:unless)
  
  # Build event requirement
  @event_requirement = build_matcher(options, :on, :except_on)
  
  # Build success requirement
  @success_requirement = options.delete(:include_failures) ? AllMatcher.instance : WhitelistMatcher.new([true])
  
  if (options.keys - [:from, :to, :on, :except_from, :except_to, :except_on]).empty?
    # Explicit from/to requirements specified
    @state_requirements = [{:from => build_matcher(options, :from, :except_from), :to => build_matcher(options, :to, :except_to)}]
  else
    # Separate out the event requirement
    options.delete(:on)
    options.delete(:except_on)
    
    # Implicit from/to requirements specified
    @state_requirements = options.collect do |from, to|
      from = WhitelistMatcher.new(from) unless from.is_a?(Matcher)
      to = WhitelistMatcher.new(to) unless to.is_a?(Matcher)
      {:from => from, :to => to}
    end
  end
  
  # Track known states.  The order that requirements are iterated is based
  # on the priority in which tracked states should be added.
  @known_states = []
  @state_requirements.each do |state_requirement|
    [:from, :to].each {|option| @known_states |= state_requirement[option].values}
  end
end

Instance Attribute Details

#event_requirementObject (readonly)

The requirement for verifying the event being guarded



21
22
23
# File 'lib/state_machine/guard.rb', line 21

def event_requirement
  @event_requirement
end

#if_conditionObject (readonly)

The condition that must be met on an object



15
16
17
# File 'lib/state_machine/guard.rb', line 15

def if_condition
  @if_condition
end

#known_statesObject (readonly)

A list of all of the states known to this guard. This will pull states from the following options (in the same order):

  • from / except_from

  • to / except_to



34
35
36
# File 'lib/state_machine/guard.rb', line 34

def known_states
  @known_states
end

#state_requirementsObject (readonly)

One or more requirements for verifying the states being guarded. All requirements contain a mapping of => matcher, :to => matcher.



25
26
27
# File 'lib/state_machine/guard.rb', line 25

def state_requirements
  @state_requirements
end

#success_requirementObject (readonly)

The requirement for verifying the success of the event



28
29
30
# File 'lib/state_machine/guard.rb', line 28

def success_requirement
  @success_requirement
end

#unless_conditionObject (readonly)

The condition that must not be met on an object



18
19
20
# File 'lib/state_machine/guard.rb', line 18

def unless_condition
  @unless_condition
end

Instance Method Details

#draw(graph, event, valid_states) ⇒ Object

Draws a representation of this guard on the given graph. This will draw an edge between every state this guard matches from to either the configured to state or, if none specified, then a loopback to the from state.

For example, if the following from states are configured:

  • idling

  • first_gear

  • backing_up

…and the to state is parked, then the following edges will be created:

  • idling -> parked

  • first_gear -> parked

  • backing_up -> parked

Each edge will be labeled with the name of the event that would cause the transition.

The collection of edges generated on the graph will be returned.



147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/state_machine/guard.rb', line 147

def draw(graph, event, valid_states)
  state_requirements.inject([]) do |edges, state_requirement|
    # From states determined based on the known valid states
    from_states = state_requirement[:from].filter(valid_states)
    
    # If a to state is not specified, then it's a loopback and each from
    # state maps back to itself
    if state_requirement[:to].values.empty?
      loopback = true
    else
      to_state = state_requirement[:to].values.first
      to_state = to_state ? to_state.to_s : 'nil'
      loopback = false
    end
    
    # Generate an edge between each from and to state
    from_states.each do |from_state|
      from_state = from_state ? from_state.to_s : 'nil'
      edges << graph.add_edge(from_state, loopback ? from_state : to_state, :label => event.to_s)
    end
    
    edges
  end
end

#match(object, query = {}) ⇒ Object

Attempts to match the given object / query against the set of requirements configured for this guard. In addition to matching the event, from state, and to state, this will also check whether the configured :if/:unless conditions pass on the given object.

If a match is found, then the event/state requirements that the query passed successfully will be returned. Otherwise, nil is returned if there was no match.

Query options:

  • :from - One or more states being transitioned from. If none are specified, then this will always match.

  • :to - One or more states being transitioned to. If none are specified, then this will always match.

  • :on - One or more events that fired the transition. If none are specified, then this will always match.

Examples

guard = StateMachine::Guard.new(:parked => :idling, :on => :ignite)

guard.match(object, :on => :ignite) # => {:to => ..., :from => ..., :on => ...}
guard.match(object, :on => :park)   # => nil


122
123
124
125
126
# File 'lib/state_machine/guard.rb', line 122

def match(object, query = {})
  if (match = match_query(query)) && matches_conditions?(object)
    match
  end
end

#matches?(object, query = {}) ⇒ Boolean

Determines whether the given object / query matches the requirements configured for this guard. In addition to matching the event, from state, and to state, this will also check whether the configured :if/:unless conditions pass on the given object.

Examples

guard = StateMachine::Guard.new(:parked => :idling, :on => :ignite)

# Successful
guard.matches?(object, :on => :ignite)                                    # => true
guard.matches?(object, :from => nil)                                      # => true
guard.matches?(object, :from => :parked)                                  # => true
guard.matches?(object, :to => :idling)                                    # => true
guard.matches?(object, :from => :parked, :to => :idling)                  # => true
guard.matches?(object, :on => :ignite, :from => :parked, :to => :idling)  # => true

# Unsuccessful
guard.matches?(object, :on => :park)                                      # => false
guard.matches?(object, :from => :idling)                                  # => false
guard.matches?(object, :to => :first_gear)                                # => false
guard.matches?(object, :from => :parked, :to => :first_gear)              # => false
guard.matches?(object, :on => :park, :from => :parked, :to => :idling)    # => false

Returns:

  • (Boolean)


95
96
97
# File 'lib/state_machine/guard.rb', line 95

def matches?(object, query = {})
  !match(object, query).nil?
end