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)

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



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

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

#after(*a, &b) ⇒ Object



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

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

#after_all(*a) ⇒ Object



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

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

#after_everythingObject



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

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



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

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

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



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

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’



278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
# File 'lib/lathe.rb', line 278

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



304
305
306
307
308
309
310
311
312
313
314
315
316
317
# File 'lib/lathe.rb', line 304

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

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

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



209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
# File 'lib/lathe.rb', line 209

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



247
248
249
# File 'lib/lathe.rb', line 247

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.



90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/lathe.rb', line 90

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



337
338
339
340
# File 'lib/lathe.rb', line 337

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

#execute(*a, &b) ⇒ Object



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

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]



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

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



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

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

#initial_state(*args, &block) ⇒ Object

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



235
236
237
238
# File 'lib/lathe.rb', line 235

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



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

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

#on_exit(*a, &b) ⇒ Object



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

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


174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/lathe.rb', line 174

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



241
242
243
244
# File 'lib/lathe.rb', line 241

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

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



132
133
134
135
136
137
138
139
140
141
# File 'lib/lathe.rb', line 132

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



327
328
329
330
# File 'lib/lathe.rb', line 327

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 [:end, :finale, :intermission]



270
271
272
273
# File 'lib/lathe.rb', line 270

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

#tool(*modules) ⇒ Object

helpers are mixed into all binding / transition contexts



71
72
73
74
75
# File 'lib/lathe.rb', line 71

def tool( *modules )
  machine.tool *modules
  # inject them into self for immediate use
  modules.flatten.extend( ToolArray ).inject_into( self )
end

#transitions(options = {}) ⇒ Object

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



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/lathe.rb', line 110

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



363
364
365
366
367
368
369
370
371
# File 'lib/lathe.rb', line 363

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