module StateMachines # Represents a collection of state machines for a class class MachineCollection < Hash # Initializes the state of each machine in the given object. This can allow # states to be initialized in two groups: static and dynamic. For example: # # machines.initialize_states(object) do # # After static state initialization, before dynamic state initialization # end # # If no block is provided, then all states will still be initialized. # # Valid configuration options: # * :static - Whether to initialize static states. Unless set to # false, the state will be initialized regardless of its current value. # Default is true. # * :dynamic - Whether to initialize dynamic states. If set to # :force, the state will be initialized regardless of its current value. # Default is true. # * :to - A hash to write the initialized state to instead of # writing to the object. Default is to write directly to the object. def initialize_states(object, options = {}, attributes = {}) options.assert_valid_keys( :static, :dynamic, :to) options = {:static => true, :dynamic => true}.merge(options) result = yield if block_given? each_value do |machine| unless machine.dynamic_initial_state? force = options[:static] == :force || !attributes.keys.include?(machine.attribute) machine.initialize_state(object, force: force, :to => options[:to]) end end if options[:static] each_value do |machine| machine.initialize_state(object, :force => options[:dynamic] == :force, :to => options[:to]) if machine.dynamic_initial_state? end if options[:dynamic] result end # Runs one or more events in parallel on the given object. See # StateMachines::InstanceMethods#fire_events for more information. 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 {|name, machine| event = machine.events[event_name, :qualified_name]} raise(InvalidEvent.new(object, event_name)) unless event # Get the transition that will be performed for the event unless transition = event.transition_for(object) event.on_failure(object) end transition end.compact # Run the events in parallel only if valid transitions were found for # all of them if events.length == transitions.length TransitionCollection.new(transitions, :actions => run_action).perform else false end end # Builds the collection of transitions for all event attributes defined on # the given object. This will only include events whose machine actions # match the one specified. # # These should only be fired as a result of the action being run. def transitions(object, action, options = {}) transitions = map do |name, machine| machine.events.attribute_transition_for(object, true) if machine.action == action end AttributeTransitionCollection.new(transitions.compact, options) end end end