class EnumStateMachine::YARD::Handlers::Machine
Handles and processes state_machine
Attributes
The generated state machine
Public Instance Methods
# File lib/enum_state_machine/yard/handlers/machine.rb 14 def process 15 # Cross-file storage for state machines 16 globals.state_machines ||= Hash.new {|h, k| h[k] = {}} 17 namespace['state_machines'] ||= {} 18 19 # Create new machine 20 klass = inherited_machine ? Class.new(inherited_machine.owner_class) : Class.new { extend EnumStateMachine::MacroMethods } 21 @machine = klass.state_machine(name, options) {} 22 23 # Track the state machine 24 globals.state_machines[namespace.name][name] = machine 25 namespace['state_machines'][name] = {:name => name, :description => statement.docstring} 26 27 # Parse the block 28 parse_block(statement.last.last, :owner => machine) 29 30 # Draw the machine for reference in the template 31 file = Tempfile.new(['enum_state_machine', '.png']) 32 begin 33 if machine.draw(:name => File.basename(file.path, '.png'), :path => File.dirname(file.path), :orientation => 'landscape') 34 namespace['state_machines'][name][:image] = file.read 35 end 36 ensure 37 # Clean up tempfile 38 file.close 39 file.unlink 40 end 41 42 # Define auto-generated methods 43 define_macro_methods 44 define_state_methods 45 define_event_methods 46 end
Protected Instance Methods
Defines auto-generated event methods for the given machine
# File lib/enum_state_machine/yard/handlers/machine.rb 279 def define_event_methods 280 machine.events.each do |event| 281 next if inherited_machine && inherited_machine.events[event.name] 282 283 # Event query 284 register(m = ::YARD::CodeObjects::MethodObject.new(namespace, "can_#{event.qualified_name}?")) 285 m.docstring = [ 286 "Checks whether #{event.name.inspect} can be fired.", 287 "@param [Hash] requirements The transition requirements to test against", 288 "@option requirements [#{state_type}] :from (the current state) One or more initial states", 289 "@option requirements [#{state_type}] :to One or more target states", 290 "@option requirements [Boolean] :guard Whether to guard transitions with conditionals", 291 "@return [Boolean] +true+ if #{event.name.inspect} can be fired, otherwise +false+" 292 ] 293 m.parameters = [["requirements", "{}"]] 294 295 # Event transition 296 register(m = ::YARD::CodeObjects::MethodObject.new(namespace, "#{event.qualified_name}_transition")) 297 m.docstring = [ 298 "Gets the next transition that would be performed if #{event.name.inspect} were to be fired.", 299 "@param [Hash] requirements The transition requirements to test against", 300 "@option requirements [#{state_type}] :from (the current state) One or more initial states", 301 "@option requirements [#{state_type}] :to One or more target states", 302 "@option requirements [Boolean] :guard Whether to guard transitions with conditionals", 303 "@return [EnumStateMachine::Transition] The transition that would be performed or +nil+" 304 ] 305 m.parameters = [["requirements", "{}"]] 306 307 # Fire event 308 register(m = ::YARD::CodeObjects::MethodObject.new(namespace, event.qualified_name)) 309 m.docstring = [ 310 "Fires the #{event.name.inspect} event.", 311 "@param [Array] args Optional arguments to include in transition callbacks", 312 "@return [Boolean] +true+ if the transition succeeds, otherwise +false+" 313 ] 314 m.parameters = ["*args"] 315 316 # Fire event (raises exception) 317 register(m = ::YARD::CodeObjects::MethodObject.new(namespace, "#{event.qualified_name}!")) 318 m.docstring = [ 319 "Fires the #{event.name.inspect} event, raising an exception if it fails.", 320 "@param [Array] args Optional arguments to include in transition callbacks", 321 "@return [Boolean] +true+ if the transition succeeds", 322 "@raise [EnumStateMachine::InvalidTransition] If the transition fails" 323 ] 324 m.parameters = ["*args"] 325 end 326 end
Defines auto-generated macro methods for the given machine
# File lib/enum_state_machine/yard/handlers/machine.rb 136 def define_macro_methods 137 return if inherited_machine 138 139 # Human state name lookup 140 register(m = ::YARD::CodeObjects::MethodObject.new(namespace, "human_#{machine.attribute(:name)}", :class)) 141 m.docstring = [ 142 "Gets the humanized name for the given state.", 143 "@param [#{state_type}] state The state to look up", 144 "@return [String] The human state name" 145 ] 146 m.parameters = ["state"] 147 148 # Human event name lookup 149 register(m = ::YARD::CodeObjects::MethodObject.new(namespace, "human_#{machine.attribute(:event_name)}", :class)) 150 m.docstring = [ 151 "Gets the humanized name for the given event.", 152 "@param [#{event_type}] event The event to look up", 153 "@return [String] The human event name" 154 ] 155 m.parameters = ["event"] 156 157 # Only register attributes when the accessor isn't explicitly defined 158 # by the class / superclass *and* isn't defined by inference from the 159 # ORM being used 160 unless integration || instance_attributes.include?(machine.attribute.to_sym) 161 attribute = machine.attribute 162 namespace.attributes[:instance][attribute] = {} 163 164 # Machine attribute getter 165 register(m = ::YARD::CodeObjects::MethodObject.new(namespace, attribute)) 166 namespace.attributes[:instance][attribute][:read] = m 167 m.docstring = [ 168 "Gets the current attribute value for the machine", 169 "@return The attribute value" 170 ] 171 172 # Machine attribute setter 173 register(m = ::YARD::CodeObjects::MethodObject.new(namespace, "#{attribute}=")) 174 namespace.attributes[:instance][attribute][:write] = m 175 m.docstring = [ 176 "Sets the current value for the machine", 177 "@param new_#{attribute} The new value to set" 178 ] 179 m.parameters = ["new_#{attribute}"] 180 end 181 182 if integration && integration.defaults[:action] && !options.include?(:action) || options[:action] 183 attribute = "#{machine.name}_event" 184 namespace.attributes[:instance][attribute] = {} 185 186 # Machine event attribute getter 187 register(m = ::YARD::CodeObjects::MethodObject.new(namespace, attribute)) 188 namespace.attributes[:instance][attribute][:read] = m 189 m.docstring = [ 190 "Gets the current event attribute value for the machine", 191 "@return The event attribute value" 192 ] 193 194 # Machine event attribute setter 195 register(m = ::YARD::CodeObjects::MethodObject.new(namespace, "#{attribute}=")) 196 namespace.attributes[:instance][attribute][:write] = m 197 m.docstring = [ 198 "Sets the current value for the machine", 199 "@param new_#{attribute} The new value to set" 200 ] 201 m.parameters = ["new_#{attribute}"] 202 end 203 204 # Presence query 205 register(m = ::YARD::CodeObjects::MethodObject.new(namespace, "#{machine.name}?")) 206 m.docstring = [ 207 "Checks the given state name against the current state.", 208 "@param [#{state_type}] state_name The name of the state to check", 209 "@return [Boolean] True if they are the same state, otherwise false", 210 "@raise [IndexError] If the state name is invalid" 211 ] 212 m.parameters = ["state_name"] 213 214 # Internal state name 215 register(m = ::YARD::CodeObjects::MethodObject.new(namespace, machine.attribute(:name))) 216 m.docstring = [ 217 "Gets the internal name of the state for the current value.", 218 "@return [#{state_type}] The internal name of the state" 219 ] 220 221 # Human state name 222 register(m = ::YARD::CodeObjects::MethodObject.new(namespace, "human_#{machine.attribute(:name)}")) 223 m.docstring = [ 224 "Gets the human-readable name of the state for the current value.", 225 "@return [String] The human-readable state name" 226 ] 227 228 # Available events 229 register(m = ::YARD::CodeObjects::MethodObject.new(namespace, machine.attribute(:events))) 230 m.docstring = [ 231 "Gets the list of events that can be fired on the current #{machine.name} (uses the *unqualified* event names)", 232 "@param [Hash] requirements The transition requirements to test against", 233 "@option requirements [#{state_type}] :from (the current state) One or more initial states", 234 "@option requirements [#{state_type}] :to One or more target states", 235 "@option requirements [#{event_type}] :on One or more events that fire the transition", 236 "@option requirements [Boolean] :guard Whether to guard transitions with conditionals", 237 "@return [Array<#{event_type}>] The list of event names" 238 ] 239 m.parameters = [["requirements", "{}"]] 240 241 # Available transitions 242 register(m = ::YARD::CodeObjects::MethodObject.new(namespace, machine.attribute(:transitions))) 243 m.docstring = [ 244 "Gets the list of transitions that can be made for the current #{machine.name}", 245 "@param [Hash] requirements The transition requirements to test against", 246 "@option requirements [#{state_type}] :from (the current state) One or more initial states", 247 "@option requirements [#{state_type}] :to One or more target states", 248 "@option requirements [#{event_type}] :on One or more events that fire the transition", 249 "@option requirements [Boolean] :guard Whether to guard transitions with conditionals", 250 "@return [Array<EnumStateMachine::Transition>] The available transitions" 251 ] 252 m.parameters = [["requirements", "{}"]] 253 254 # Available transition paths 255 register(m = ::YARD::CodeObjects::MethodObject.new(namespace, machine.attribute(:paths))) 256 m.docstring = [ 257 "Gets the list of sequences of transitions that can be run for the current #{machine.name}", 258 "@param [Hash] requirements The transition requirements to test against", 259 "@option requirements [#{state_type}] :from (the current state) The initial state", 260 "@option requirements [#{state_type}] :to The target state", 261 "@option requirements [Boolean] :deep Whether to enable deep searches for the target state", 262 "@option requirements [Boolean] :guard Whether to guard transitions with conditionals", 263 "@return [EnumStateMachine::PathCollection] The collection of paths" 264 ] 265 m.parameters = [["requirements", "{}"]] 266 267 # Generic event fire 268 register(m = ::YARD::CodeObjects::MethodObject.new(namespace, "fire_#{machine.attribute(:event)}")) 269 m.docstring = [ 270 "Fires an arbitrary #{machine.name} event with the given argument list", 271 "@param [#{event_type}] event The name of the event to fire", 272 "@param args Optional arguments to include in the transition", 273 "@return [Boolean] +true+ if the event succeeds, otherwise +false+" 274 ] 275 m.parameters = ["event", "*args"] 276 end
Defines auto-generated state methods for the given machine
# File lib/enum_state_machine/yard/handlers/machine.rb 329 def define_state_methods 330 machine.states.each do |state| 331 next if inherited_machine && inherited_machine.states[state.name] || !state.name 332 333 # State query 334 register(m = ::YARD::CodeObjects::MethodObject.new(namespace, "#{state.qualified_name}?")) 335 m.docstring = [ 336 "Checks whether #{state.name.inspect} is the current state.", 337 "@return [Boolean] +true+ if this is the current state, otherwise +false+" 338 ] 339 end 340 end
Gets the class type being used to define events. Default is “Symbol”.
# File lib/enum_state_machine/yard/handlers/machine.rb 131 def event_type 132 @event_type ||= machine.events.any? ? machine.events.first.name.class.to_s : 'Symbol' 133 end
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/enum_state_machine/yard/handlers/machine.rb 85 def inherited_machine 86 @inherited_machine ||= begin 87 namespace.inheritance_tree.each do |ancestor| 88 begin 89 ensure_loaded!(ancestor) 90 rescue ::YARD::Handlers::NamespaceMissingError 91 # Ignore: just means that we can't access an ancestor 92 end 93 end 94 95 # Find the first ancestor that has the machine 96 loaded_superclasses.detect do |superclass| 97 if superclass != namespace 98 machine = globals.state_machines[superclass.name][name] 99 break machine if machine 100 end 101 end 102 end 103 end
Gets a list of all attributes for the current class, including those that are inherited
# File lib/enum_state_machine/yard/handlers/machine.rb 113 def instance_attributes 114 attributes = {} 115 loaded_superclasses.each {|superclass| attributes.merge!(superclass.instance_attributes)} 116 attributes 117 end
Gets the type of ORM integration being used based on the list of ancestors (including mixins)
# File lib/enum_state_machine/yard/handlers/machine.rb 121 def integration 122 @integration ||= Integrations.match_ancestors(namespace.inheritance_tree(true).map {|ancestor| ancestor.path}) 123 end
Gets members of this class's superclasses have already been loaded by YARD
# File lib/enum_state_machine/yard/handlers/machine.rb 107 def loaded_superclasses 108 namespace.inheritance_tree.select {|ancestor| ancestor.is_a?(::YARD::CodeObjects::ClassObject)} 109 end
Extracts the machine name's
# File lib/enum_state_machine/yard/handlers/machine.rb 50 def name 51 @name ||= begin 52 ast = statement.parameters.first 53 if ast && [:symbol_literal, :string_literal].include?(ast.type) 54 extract_node_name(ast) 55 else 56 :state 57 end 58 end 59 end
Extracts the machine options. Note that this will only extract a subset of the options supported.
# File lib/enum_state_machine/yard/handlers/machine.rb 63 def options 64 @options ||= begin 65 options = {} 66 ast = statement.parameters(false).last 67 68 if !inherited_machine && ast && ![:symbol_literal, :string_literal].include?(ast.type) 69 ast.children.each do |assoc| 70 # Only extract important options 71 key = extract_node_name(assoc[0]) 72 next unless [:initial, :attribute, :namespace, :action].include?(key) 73 74 value = extract_node_name(assoc[1]) 75 options[key] = value 76 end 77 end 78 79 options 80 end 81 end
Gets the class type being used to define states. Default is “Symbol”.
# File lib/enum_state_machine/yard/handlers/machine.rb 126 def state_type 127 @state_type ||= machine.states.any? ? machine.states.map {|state| state.name}.compact.first.class.to_s : 'Symbol' 128 end