class StateMachine::YARD::Handlers::Machine

Handles and processes state_machine

Attributes

machine[R]

The generated state machine

Public Instance Methods

process() click to toggle source
# File lib/state_machine/yard/handlers/machine.rb, line 14
def process
  # Cross-file storage for state machines
  globals.state_machines ||= Hash.new {|h, k| h[k] = {}}
  namespace['state_machines'] ||= {}
  
  # Create new machine
  klass = inherited_machine ? Class.new(inherited_machine.owner_class) : Class.new { extend StateMachine::MacroMethods }
  @machine = klass.state_machine(name, options) {}
  
  # Track the state machine
  globals.state_machines[namespace.name][name] = machine
  namespace['state_machines'][name] = {:name => name, :description => statement.docstring}
  
  # Parse the block
  parse_block(statement.last.last, :owner => machine)
  
  # Draw the machine for reference in the template
  file = Tempfile.new(['state_machine', '.png'])
  begin
    if machine.draw(:name => File.basename(file.path, '.png'), :path => File.dirname(file.path), :orientation => 'landscape')
      namespace['state_machines'][name][:image] = file.read
    end
  ensure
    # Clean up tempfile
    file.close
    file.unlink
  end
  
  # Define auto-generated methods
  define_macro_methods
  define_state_methods
  define_event_methods
end

Protected Instance Methods

define_event_methods() click to toggle source

Defines auto-generated event methods for the given machine

# File lib/state_machine/yard/handlers/machine.rb, line 279
def define_event_methods
  machine.events.each do |event|
    next if inherited_machine && inherited_machine.events[event.name]
    
    # Event query
    register(m = ::YARD::CodeObjects::MethodObject.new(namespace, "can_#{event.qualified_name}?"))
    m.docstring = [
      "Checks whether #{event.name.inspect} can be fired.",
      "@param [Hash] requirements The transition requirements to test against",
      "@option requirements [#{state_type}] :from (the current state) One or more initial states",
      "@option requirements [#{state_type}] :to One or more target states",
      "@option requirements [Boolean] :guard Whether to guard transitions with conditionals",
      "@return [Boolean] +true+ if #{event.name.inspect} can be fired, otherwise +false+"
    ]
    m.parameters = [["requirements", "{}"]]
    
    # Event transition
    register(m = ::YARD::CodeObjects::MethodObject.new(namespace, "#{event.qualified_name}_transition"))
    m.docstring = [
      "Gets the next transition that would be performed if #{event.name.inspect} were to be fired.",
      "@param [Hash] requirements The transition requirements to test against",
      "@option requirements [#{state_type}] :from (the current state) One or more initial states",
      "@option requirements [#{state_type}] :to One or more target states",
      "@option requirements [Boolean] :guard Whether to guard transitions with conditionals",
      "@return [StateMachine::Transition] The transition that would be performed or +nil+"
    ]
    m.parameters = [["requirements", "{}"]]
    
    # Fire event
    register(m = ::YARD::CodeObjects::MethodObject.new(namespace, event.qualified_name))
    m.docstring = [
      "Fires the #{event.name.inspect} event.",
      "@param [Array] args Optional arguments to include in transition callbacks",
      "@return [Boolean] +true+ if the transition succeeds, otherwise +false+"
    ]
    m.parameters = ["*args"]
    
    # Fire event (raises exception)
    register(m = ::YARD::CodeObjects::MethodObject.new(namespace, "#{event.qualified_name}!"))
    m.docstring = [
      "Fires the #{event.name.inspect} event, raising an exception if it fails.",
      "@param [Array] args Optional arguments to include in transition callbacks",
      "@return [Boolean] +true+ if the transition succeeds",
      "@raise [StateMachine::InvalidTransition] If the transition fails"
    ]
    m.parameters = ["*args"]
  end
end
define_macro_methods() click to toggle source

Defines auto-generated macro methods for the given machine

# File lib/state_machine/yard/handlers/machine.rb, line 136
def define_macro_methods
  return if inherited_machine
  
  # Human state name lookup
  register(m = ::YARD::CodeObjects::MethodObject.new(namespace, "human_#{machine.attribute(:name)}", :class))
  m.docstring = [
    "Gets the humanized name for the given state.",
    "@param [#{state_type}] state The state to look up",
    "@return [String] The human state name"
  ]
  m.parameters = ["state"]
  
  # Human event name lookup
  register(m = ::YARD::CodeObjects::MethodObject.new(namespace, "human_#{machine.attribute(:event_name)}", :class))
  m.docstring = [
    "Gets the humanized name for the given event.",
    "@param [#{event_type}] event The event to look up",
    "@return [String] The human event name"
  ]
  m.parameters = ["event"]
  
  # Only register attributes when the accessor isn't explicitly defined
  # by the class / superclass *and* isn't defined by inference from the
  # ORM being used
  unless integration || instance_attributes.include?(machine.attribute.to_sym)
    attribute = machine.attribute
    namespace.attributes[:instance][attribute] = {}
    
    # Machine attribute getter
    register(m = ::YARD::CodeObjects::MethodObject.new(namespace, attribute))
    namespace.attributes[:instance][attribute][:read] = m
    m.docstring = [
      "Gets the current attribute value for the machine",
      "@return The attribute value"
    ]
    
    # Machine attribute setter
    register(m = ::YARD::CodeObjects::MethodObject.new(namespace, "#{attribute}="))
    namespace.attributes[:instance][attribute][:write] = m
    m.docstring = [
      "Sets the current value for the machine",
      "@param new_#{attribute} The new value to set"
    ]
    m.parameters = ["new_#{attribute}"]
  end
  
  if integration && integration.defaults[:action] && !options.include?(:action) || options[:action]
    attribute = "#{machine.name}_event"
    namespace.attributes[:instance][attribute] = {}
    
    # Machine event attribute getter
    register(m = ::YARD::CodeObjects::MethodObject.new(namespace, attribute))
    namespace.attributes[:instance][attribute][:read] = m
    m.docstring = [
      "Gets the current event attribute value for the machine",
      "@return The event attribute value"
    ]
    
    # Machine event attribute setter
    register(m = ::YARD::CodeObjects::MethodObject.new(namespace, "#{attribute}="))
    namespace.attributes[:instance][attribute][:write] = m
    m.docstring = [
      "Sets the current value for the machine",
      "@param new_#{attribute} The new value to set"
    ]
    m.parameters = ["new_#{attribute}"]
  end
  
  # Presence query
  register(m = ::YARD::CodeObjects::MethodObject.new(namespace, "#{machine.name}?"))
  m.docstring = [
    "Checks the given state name against the current state.",
    "@param [#{state_type}] state_name The name of the state to check",
    "@return [Boolean] True if they are the same state, otherwise false",
    "@raise [IndexError] If the state name is invalid"
  ]
  m.parameters = ["state_name"]
  
  # Internal state name
  register(m = ::YARD::CodeObjects::MethodObject.new(namespace, machine.attribute(:name)))
  m.docstring = [
    "Gets the internal name of the state for the current value.",
    "@return [#{state_type}] The internal name of the state"
  ]
  
  # Human state name
  register(m = ::YARD::CodeObjects::MethodObject.new(namespace, "human_#{machine.attribute(:name)}"))
  m.docstring = [
    "Gets the human-readable name of the state for the current value.",
    "@return [String] The human-readable state name"
  ]
  
  # Available events
  register(m = ::YARD::CodeObjects::MethodObject.new(namespace, machine.attribute(:events)))
  m.docstring = [
    "Gets the list of events that can be fired on the current #{machine.name} (uses the *unqualified* event names)",
    "@param [Hash] requirements The transition requirements to test against",
    "@option requirements [#{state_type}] :from (the current state) One or more initial states",
    "@option requirements [#{state_type}] :to One or more target states",
    "@option requirements [#{event_type}] :on One or more events that fire the transition",
    "@option requirements [Boolean] :guard Whether to guard transitions with conditionals",
    "@return [Array<#{event_type}>] The list of event names"
  ]
  m.parameters = [["requirements", "{}"]]
  
  # Available transitions
  register(m = ::YARD::CodeObjects::MethodObject.new(namespace, machine.attribute(:transitions)))
  m.docstring = [
    "Gets the list of transitions that can be made for the current #{machine.name}",
    "@param [Hash] requirements The transition requirements to test against",
    "@option requirements [#{state_type}] :from (the current state) One or more initial states",
    "@option requirements [#{state_type}] :to One or more target states",
    "@option requirements [#{event_type}] :on One or more events that fire the transition",
    "@option requirements [Boolean] :guard Whether to guard transitions with conditionals",
    "@return [Array<StateMachine::Transition>] The available transitions"
  ]
  m.parameters = [["requirements", "{}"]]
  
  # Available transition paths
  register(m = ::YARD::CodeObjects::MethodObject.new(namespace, machine.attribute(:paths)))
  m.docstring = [
    "Gets the list of sequences of transitions that can be run for the current #{machine.name}",
    "@param [Hash] requirements The transition requirements to test against",
    "@option requirements [#{state_type}] :from (the current state) The initial state",
    "@option requirements [#{state_type}] :to The target state",
    "@option requirements [Boolean] :deep Whether to enable deep searches for the target state",
    "@option requirements [Boolean] :guard Whether to guard transitions with conditionals",
    "@return [StateMachine::PathCollection] The collection of paths"
  ]
  m.parameters = [["requirements", "{}"]]
  
  # Generic event fire
  register(m = ::YARD::CodeObjects::MethodObject.new(namespace, "fire_#{machine.attribute(:event)}"))
  m.docstring = [
    "Fires an arbitrary #{machine.name} event with the given argument list",
    "@param [#{event_type}] event The name of the event to fire",
    "@param args Optional arguments to include in the transition",
    "@return [Boolean] +true+ if the event succeeds, otherwise +false+"
  ]
  m.parameters = ["event", "*args"]
end
define_state_methods() click to toggle source

Defines auto-generated state methods for the given machine

# File lib/state_machine/yard/handlers/machine.rb, line 329
def define_state_methods
  machine.states.each do |state|
    next if inherited_machine && inherited_machine.states[state.name] || !state.name
    
    # State query
    register(m = ::YARD::CodeObjects::MethodObject.new(namespace, "#{state.qualified_name}?"))
    m.docstring = [
      "Checks whether #{state.name.inspect} is the current state.",
      "@return [Boolean] +true+ if this is the current state, otherwise +false+"
    ]
  end
end
event_type() click to toggle source

Gets the class type being used to define events. Default is “Symbol”.

# File lib/state_machine/yard/handlers/machine.rb, line 131
def event_type
  @event_type ||= machine.events.any? ? machine.events.first.name.class.to_s : 'Symbol'
end
inherited_machine() click to toggle source

Gets the machine that was inherited from a superclass. This also ensures each ancestor has been loaded prior to looking up their definitions.

# File lib/state_machine/yard/handlers/machine.rb, line 85
def inherited_machine
  @inherited_machine ||= begin
    namespace.inheritance_tree.each do |ancestor|
      begin
        ensure_loaded!(ancestor)
      rescue ::YARD::Handlers::NamespaceMissingError
        # Ignore: just means that we can't access an ancestor
      end
    end
    
    # Find the first ancestor that has the machine
    loaded_superclasses.detect do |superclass|
      if superclass != namespace
        machine = globals.state_machines[superclass.name][name]
        break machine if machine
      end
    end
  end
end
instance_attributes() click to toggle source

Gets a list of all attributes for the current class, including those that are inherited

# File lib/state_machine/yard/handlers/machine.rb, line 113
def instance_attributes
  attributes = {}
  loaded_superclasses.each {|superclass| attributes.merge!(superclass.instance_attributes)}
  attributes
end
integration() click to toggle source

Gets the type of ORM integration being used based on the list of ancestors (including mixins)

# File lib/state_machine/yard/handlers/machine.rb, line 121
def integration
  @integration ||= Integrations.match_ancestors(namespace.inheritance_tree(true).map {|ancestor| ancestor.path})
end
loaded_superclasses() click to toggle source

Gets members of this class's superclasses have already been loaded by YARD

# File lib/state_machine/yard/handlers/machine.rb, line 107
def loaded_superclasses
  namespace.inheritance_tree.select {|ancestor| ancestor.is_a?(::YARD::CodeObjects::ClassObject)}
end
name() click to toggle source

Extracts the machine name's

# File lib/state_machine/yard/handlers/machine.rb, line 50
def name
  @name ||= begin
    ast = statement.parameters.first
    if ast && [:symbol_literal, :string_literal].include?(ast.type)
      extract_node_name(ast)
    else
      :state
    end
  end
end
options() click to toggle source

Extracts the machine options. Note that this will only extract a subset of the options supported.

# File lib/state_machine/yard/handlers/machine.rb, line 63
def options
  @options ||= begin
    options = {}
    ast = statement.parameters(false).last
    
    if !inherited_machine && ast && ![:symbol_literal, :string_literal].include?(ast.type)
      ast.children.each do |assoc|
        # Only extract important options
        key = extract_node_name(assoc[0])
        next unless [:initial, :attribute, :namespace, :action].include?(key)
        
        value = extract_node_name(assoc[1])
        options[key] = value
      end
    end
    
    options
  end
end
state_type() click to toggle source

Gets the class type being used to define states. Default is “Symbol”.

# File lib/state_machine/yard/handlers/machine.rb, line 126
def state_type
  @state_type ||= machine.states.any? ? machine.states.map {|state| state.name}.compact.first.class.to_s : 'Symbol'
end