class EnumStateMachine::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/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

define_event_methods() click to toggle source

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
define_macro_methods() click to toggle source

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
define_state_methods() click to toggle source

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
event_type() click to toggle source

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
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/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
instance_attributes() click to toggle source

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
integration() click to toggle source

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
loaded_superclasses() click to toggle source

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
name() click to toggle source

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
options() click to toggle source

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
state_type() click to toggle source

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