Class: StateFu::Lathe

Inherits:
Object show all
Defined in:
lib/lathe.rb

Overview

A Lathe parses and a Machine definition and returns a freshly turned Machine.

It provides the means to define the arrangement of StateFu objects ( eg States and Events) which comprise a workflow, process, lifecycle, circuit, syntax, etc.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(machine, state_or_event = nil, options = {}, &block) ⇒ Lathe

you don’t need to call this directly.



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/lathe.rb', line 16

def initialize( machine, state_or_event = nil, options={}, &block )
  @machine        = machine
  @state_or_event = state_or_event
  @options        = options.symbolize_keys!

  # extend ourself with any previously defined tools
  machine.tools.inject_into( self )

  if state_or_event
    state_or_event.apply!( options )
  end
  if block_given?
    if block.arity == 1
      if state_or_event
        yield state_or_event
      else
        raise ArgumentError
      end
    else
      instance_eval( &block )
    end
  end
end

Instance Attribute Details

#machineObject (readonly)

or contain a State or Event (a child lathe for a nested block)



13
14
15
# File 'lib/lathe.rb', line 13

def machine
  @machine
end

#optionsObject (readonly)

or contain a State or Event (a child lathe for a nested block)



13
14
15
# File 'lib/lathe.rb', line 13

def options
  @options
end

#state_or_eventObject (readonly) Also known as: context

or contain a State or Event (a child lathe for a nested block)



13
14
15
# File 'lib/lathe.rb', line 13

def state_or_event
  @state_or_event
end

Instance Method Details

#accepted(*a, &b) ⇒ Object Also known as: on_change



367
# File 'lib/lathe.rb', line 367

def accepted   *a, &b; valid_in_context State; define_hook :accepted,   *a, &b; end

#after(*a, &b) ⇒ Object



366
# File 'lib/lathe.rb', line 366

def after      *a, &b; valid_in_context Event; define_hook :after,      *a, &b; end

#after_all(*a) ⇒ Object



370
# File 'lib/lathe.rb', line 370

def after_all  *a, &b; valid_in_context nil;   define_hook :after_all,  *a, &b; end

#after_everythingObject



372
# File 'lib/lathe.rb', line 372

def after_all  *a, &b; valid_in_context nil;   define_hook :after_all,  *a, &b; end

#before(*a, &b) ⇒ Object

Bunch of silly little methods for defining events :nodoc



362
# File 'lib/lathe.rb', line 362

def before     *a, &b; valid_in_context Event; define_hook :before,     *a, &b; end

#before_all(*a, &b) ⇒ Object Also known as: before_everything



369
# File 'lib/lathe.rb', line 369

def before_all *a, &b; valid_in_context nil;   define_hook :before_all, *a, &b; end

#chain(string) ⇒ Object

define chained events and states succinctly usage: chain ‘state1 -event1-> state2 -event2-> state3’



293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
# File 'lib/lathe.rb', line 293

def chain string
  rx_word    = /([a-zA-Z0-9_]+)/
  rx_state   = /^#{rx_word}$/
  rx_event   = /^(?:-|>)#{rx_word}-?>$/
  previous   = nil
  string.split.each do |chunk|
    case chunk
    when rx_state
      current = state $1
      if previous.is_a? Event
        previous.to current
      end
    when rx_event
      current = event $1
      if previous.is_a? State
        current.from previous
      end
    else
      raise ArgumentError, "'#{chunk}' is not a valid token"
    end
    previous = current
  end
end

#connect_states(*array) ⇒ Object Also known as: connect

chain_states :a => [:b,:c], :c => :d, :c => :d chain_states :a,:b,:c,:d, :a => :c



319
320
321
322
323
324
325
326
327
328
329
330
331
332
# File 'lib/lathe.rb', line 319

def connect_states *array
  array.flatten!
  hash = array.extract_options!.symbolize_keys!
  array.inject(nil) do |origin, target|
    state target
    if origin
      event "#{origin.to_sym}_to_#{target.to_sym}", :from => origin, :to => target
    end
    origin = target
  end
  hash.each do |origin, target|
    event "#{origin.to_sym}_to_#{target.to_sym}", :from => origin, :to => target
  end
end

#context_eventObject



67
68
69
# File 'lib/lathe.rb', line 67

def context_event
  state_or_event if state_or_event.is_a?(Event)
end

#context_stateObject



63
64
65
# File 'lib/lathe.rb', line 63

def context_state
  state_or_event if state_or_event.is_a?(State)
end

#cycle(name = nil, options = {}, &block) ⇒ Object

create an event from and to the current state. Creates a loop, useful (only) for hooking behaviours onto.



223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
# File 'lib/lathe.rb', line 223

def cycle name=nil, options={}, &block
  _state = nil
  if name.is_a?(Hash) && options.empty?
    options = name
    name    = nil
  end
  if _state = options.delete(:state)
    valid_unless_nested("when :state is supplied")
  else
    _state = state_or_event
    valid_in_context( State, "unless :state is supplied" )
  end

  name ||= options.delete :on
  name ||= "cycle_#{_state.to_sym}"
  evt = define_event( name, options, &block )
  evt.from _state
  evt.to   _state
  evt
end

#define(method_name, &block) ⇒ Object Also known as: named_proc

define a named proc



261
262
263
# File 'lib/lathe.rb', line 261

def define method_name, &block
  machine.named_procs[method_name] = block
end

#event(name, options = {}, &block) ⇒ Object

Defines an event. Any options supplied will be added to the event, except :from and :to which are used to define the origin / target states. Successive invocations will update (not replace) previously defined events; origin / target states and options are always accumulated, not clobbered.

Several different styles of definition are available. Consult the specs / features for examples.



107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/lathe.rb', line 107

def event( name, options={}, &block )
  options.symbolize_keys!
  valid_in_context( State, nil )
  if nested? && state_or_event.is_a?(State) # in state block
    targets  = options.delete(:to) || options.delete(:transitions_to)
    evt      = define_event( name, options, &block )
    evt.from state_or_event unless state_or_event.nil?
    evt.to( targets ) unless targets.nil?
    evt
  else # in master lathe
    origins = options.delete( :from )
    targets = options.delete( :to ) || options.delete(:transitions_to)
    evt     = define_event( name, options, &block )
    evt.from origins unless origins.nil?
    evt.to   targets unless targets.nil?
    evt
  end
end

#events(*args, &block) ⇒ Object Also known as: all_events, each_event

Define a series of events at once, or return and iterate over all events yet defined



352
353
354
355
# File 'lib/lathe.rb', line 352

def events *args, &block
  valid_in_context nil, State
  each_state_or_event 'event', *args, &block
end

#execute(*a, &b) ⇒ Object



364
# File 'lib/lathe.rb', line 364

def execute    *a, &b; valid_in_context Event; define_hook :execute,    *a, &b; end

#from(*args, &block) ⇒ Object

set the origin state(s) of an event (or, given a hash of symbols / arrays of symbols, set both the origins and targets) from :my_origin from [:column_a, :column_b] from :eden => :armageddon from [:beginning, :prelogue] => [:ende, :prologue]



276
277
278
279
# File 'lib/lathe.rb', line 276

def from *args, &block
  valid_in_context Event
  state_or_event.from( *args, &block )
end

#helper(*modules) ⇒ Object

helpers are mixed into all binding / transition contexts



77
78
79
# File 'lib/lathe.rb', line 77

def helper( *modules )
  machine.helper *modules
end

#initial_state(*args, &block) ⇒ Object

define the initial_state (otherwise defaults to the first state mentioned)



249
250
251
252
# File 'lib/lathe.rb', line 249

def initial_state *args, &block
  valid_unless_nested()
  machine.initial_state= state( *args, &block)
end

#master?Boolean

is this the toplevel lathe for a machine?

Returns:

  • (Boolean)


52
53
54
# File 'lib/lathe.rb', line 52

def master?
  !nested?
end

#master_latheObject

get the top level Lathe for the machine



57
58
59
# File 'lib/lathe.rb', line 57

def master_lathe
  machine.lathe
end

#nested?Boolean Also known as: child?

a ‘child’ lathe is created by apply_to, to deal with nested blocks for states / events ( which are state_or_events )

Returns:

  • (Boolean)


46
47
48
# File 'lib/lathe.rb', line 46

def nested?
  !!state_or_event
end

#on_entry(*a, &b) ⇒ Object



365
# File 'lib/lathe.rb', line 365

def on_entry   *a, &b; valid_in_context State; define_hook :entry,      *a, &b; end

#on_exit(*a, &b) ⇒ Object



363
# File 'lib/lathe.rb', line 363

def on_exit    *a, &b; valid_in_context State; define_hook :exit,       *a, &b; end

#requires(*args, &block) ⇒ Object Also known as: guard, must, must_be, needs

define an event or state requirement. options:

:on => :entry|:exit|array (state only) - check requirement on state entry, exit or both?
   default = :entry
:message => string|proc|proc_name_symbol - message to be returned on requirement failure.
   if a proc or symbol (named proc identifier), evaluated at runtime; a proc should
   take one argument, which is a StateFu::Transition.
:msg => alias for :message, for the morbidly terse


188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/lathe.rb', line 188

def requires( *args, &block )
  valid_in_context Event, State
  options = args.extract_options!.symbolize_keys!
  options.assert_valid_keys :on, :message, :msg
  names   = args
  if block_given? && args.length > 1
    raise ArgumentError.new("cannot supply a block for multiple requirements")
  end
  on = nil
  names.each do |name|
    raise ArgumentError.new(name.inspect) unless name.is_a?(Symbol)
    case state_or_event
    when State
      on ||= [(options.delete(:on) || [:entry])].flatten
      state_or_event.entry_requirements << name if on.include?( :entry )
      state_or_event.exit_requirements  << name if on.include?( :exit  )
    when Event
      state_or_event.requirements << name
    end
    if block_given?
      machine.named_procs[name] = block
    end
    if msg = options.delete(:message) || options.delete(:msg)
      raise ArgumentError, msg.inspect unless [String, Symbol, Proc].include?(msg.class)
      machine.requirement_messages[name] = msg
    end
  end
end

#state(name, options = {}, &block) ⇒ Object

define a state; given a block, apply the block to a Lathe for the state



255
256
257
258
# File 'lib/lathe.rb', line 255

def state name, options={}, &block
  valid_unless_nested()
  define_state( name, options, &block )
end

#state_event(name, options = {}, &block) ⇒ Object



149
150
151
152
153
154
155
156
157
158
# File 'lib/lathe.rb', line 149

def state_event name, options={}, &block
  valid_in_context State
  options.symbolize_keys!
  state    = state_or_event
  targets  = options.delete(:to) || options.delete(:transitions_to)
  evt      = define_state_or_event( Event, state.own_events, name, options, &block)
  evt.from state
  evt.to(targets) unless targets.nil?
  evt
end

#states(*args, &block) ⇒ Object Also known as: all_states, each_state

Define a series of states at once, or return and iterate over all states yet defined

states :a, :b, :c, :colour => “purple” states(:ALL) do

end



342
343
344
345
# File 'lib/lathe.rb', line 342

def states *args, &block
  valid_unless_nested()
  each_state_or_event 'state', *args, &block
end

#to(*args, &block) ⇒ Object

set the target state(s) of an event to :destination to :target_a, :target_b to [:end, :finale, :intermission]



285
286
287
288
# File 'lib/lathe.rb', line 285

def to *args, &block
  valid_in_context Event
  state_or_event.to( *args, &block )
end

#tool(*modules, &block) ⇒ Object Also known as: extend_dsl

helpers are mixed into all binding / transition contexts



82
83
84
85
86
87
88
89
90
91
# File 'lib/lathe.rb', line 82

def tool( *modules, &block )
  machine.tool *modules
  if block_given?
    tool = Module.new
    tool.module_eval &block
    machine.tools << tool
  end
  # inject them into self for immediate use
  modules.flatten.extend( ToolArray ).inject_into( self )
end

#transitions(options = {}) ⇒ Object

compatibility methods for activemodel state machine ##############



127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/lathe.rb', line 127

def transitions(options={})
  valid_in_context(Event)
  options.symbolize_keys!

  target  = options[:to]
  origins = options[:from]
  hook    = options[:on_transition]
  evt     = state_or_event

  if hook
    evt.lathe() { triggers hook }
  end
  #
  # TODO do some type checking
  #
  if origins && target
    evt.add_to_sequence origins, target
  end
  evt
end

#will(*a, &b) ⇒ Object Also known as: fire, fires, firing, cause, causes, triggers, trigger



378
379
380
381
382
383
384
385
386
# File 'lib/lathe.rb', line 378

def will *a, &b
  valid_in_context State, Event
  case state_or_event
  when State
    define_hook :entry, *a, &b
  when Event
    define_hook :execute, *a, &b
  end
end