Class: StateMachine::MachineCollection

Inherits:
Hash
  • Object
show all
Defined in:
lib/state_machine/machine_collection.rb

Overview

Represents a collection of state machines for a class

Instance Method Summary collapse

Instance Method Details

#fire_event_attributes(object, action, complete = true) ⇒ Object

Runs one or more event attributes in parallel during the invocation of an action on the given object. after_transition callbacks can be optionally disabled if the events are being only partially fired (for example, when validating records in ORM integrations).

The event attributes that will be fired are based on which machines match the action that is being invoked.

Examples

class Vehicle
  include DataMapper::Resource
  property :id, Serial

  state_machine :initial => :parked do
    event :ignite do
      transition :parked => :idling
    end
  end

  state_machine :alarm_state, :namespace => 'alarm', :initial => :active do
    event :disable do
      transition all => :off
    end
  end
end

With valid events:

vehicle = Vehicle.create                      # => #<Vehicle id=1 state="parked" alarm_state="active">
vehicle.state_event = 'ignite'
vehicle.alarm_state_event = 'disable'

Vehicle.state_machines.fire_event_attributes(vehicle, :save) { true }
vehicle.state                                 # => "idling"
vehicle.state_event                           # => nil
vehicle.alarm_state                           # => "off"
vehicle.alarm_state_event                     # => nil

With invalid events:

vehicle = Vehicle.create                      # => #<Vehicle id=1 state="parked" alarm_state="active">
vehicle.state_event = 'park'
vehicle.alarm_state_event = 'disable'

Vehicle.state_machines.fire_event_attributes(vehicle, :save) { true }
vehicle.state                                 # => "parked"
vehicle.state_event                           # => nil
vehicle.alarm_state                           # => "active"
vehicle.alarm_state_event                     # => nil
vehicle.errors                                # => #<DataMapper::Validate::ValidationErrors:0xb7af9abc @errors={"state_event"=>["is invalid"]}>

With partial firing:

vehicle = Vehicle.create                      # => #<Vehicle id=1 state="parked" alarm_state="active">
vehicle.state_event = 'ignite'

Vehicle.state_machines.fire_event_attributes(vehicle, :save, false) { true }
vehicle.state                                 # => "idling"
vehicle.state_event                           # => "ignite"
vehicle.state_event_transition                # => #<StateMachine::Transition attribute=:state event=:ignite from="parked" from_name=:parked to="idling" to_name=:idling>


110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/state_machine/machine_collection.rb', line 110

def fire_event_attributes(object, action, complete = true)
  # Get the transitions to fire for each applicable machine
  transitions = map {|name, machine| machine.action == action ? machine.events.attribute_transition_for(object, true) : nil}.compact
  return yield if transitions.empty?
  
  # The value generated by the yielded block (the actual action)
  action_value = nil
  
  # Make sure all events were valid
  if result = transitions.all? {|transition| transition != false}
    begin
      result = Transition.perform(transitions, :after => complete) do
        # Prevent events from being evaluated multiple times if actions are nested
        transitions.each {|transition| transition.machine.write(object, :event, nil)}
        action_value = yield
      end
    rescue Exception
      # Revert object modifications
      transitions.each do |transition|
        transition.machine.write(object, :event, transition.event)
        transition.machine.write(object, :event_transition, nil) if complete
      end
      
      raise
    end
    
    transitions.each do |transition|
      # Revert event unless transition was successful
      transition.machine.write(object, :event, transition.event) unless complete && result
      
      # Track transition if partial transition completed successfully
      transition.machine.write(object, :event_transition, !complete && result ? transition : nil)
    end
  end
  
  action_value.nil? ? result : action_value
end

#fire_events(object, *events) ⇒ Object

Runs one or more events in parallel on the given object. See StateMachine::InstanceMethods#fire_events for more information.



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/state_machine/machine_collection.rb', line 18

def fire_events(object, *events)
  run_action = [true, false].include?(events.last) ? events.pop : true
  
  # Generate the transitions to run for each event
  transitions = events.collect do |event_name|
    # Find the actual event being run
    event = nil
    detect do |name, machine|
      event = machine.events[event_name, :qualified_name]
    end
    
    raise InvalidEvent, "#{event_name.inspect} is an unknown state machine event" unless event
    
    # Get the transition that will be performed for the event
    unless transition = event.transition_for(object)
      machine = event.machine
      machine.invalidate(object, :state, :invalid_transition, [[:event, event_name]])
    end
    
    transition
  end.compact
  
  # Run the events in parallel only if valid transitions were found for
  # all of them
  if events.length == transitions.length
    Transition.perform_within_transaction(transitions, :action => run_action)
  else
    false
  end
end

#initialize_states(object, options = {}) ⇒ Object

Initializes the state of each machine in the given object. Initial values are only set if the machine’s attribute doesn’t already exist (which must mean the defaults are being skipped)



7
8
9
10
11
12
13
14
# File 'lib/state_machine/machine_collection.rb', line 7

def initialize_states(object, options = {})
  each_value do |machine|
    if !options.include?(:dynamic) || machine.dynamic_initial_state? == options[:dynamic]
      value = machine.read(object, :state)
      machine.write(object, :state, machine.initial_state(object).value) if value.nil? || value.respond_to?(:empty?) && value.empty?
    end
  end
end