Class: StateFu::Binding

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

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(machine, object, method_name, options = {}) ⇒ Binding

the constructor should not be called manually; a binding is returned when an instance of a class with a StateFu::Machine calls:

instance.#state_fu (for the default machine which is called :state_fu), instance.#state_fu(:<machine_name>) ,or instance.#<machine_name>



14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/binding.rb', line 14

def initialize( machine, object, method_name, options={} )
  @machine       = machine
  @object        = object
  @method_name   = method_name
  @transitions   = []
  @options       = options.symbolize_keys!
  if options[:singleton]
    @target      = object
  else
    @target      = object.class
    @options     = @target.state_fu_options[@method_name].merge(options)
  end
  @field_name    = @options.delete(:field_name) || raise("No field_name supplied")
  @persister     = Persistence.for self

  # define event methods on this binding and its @object
  MethodFactory.new(self).install!
  @machine.helpers.inject_into self
end

Instance Attribute Details

#field_nameObject (readonly)

Returns the value of attribute field_name



4
5
6
# File 'lib/binding.rb', line 4

def field_name
  @field_name
end

#machineObject (readonly) Also known as: workflow, state_machine

Returns the value of attribute machine



4
5
6
# File 'lib/binding.rb', line 4

def machine
  @machine
end

#method_nameObject (readonly)

Returns the value of attribute method_name



4
5
6
# File 'lib/binding.rb', line 4

def method_name
  @method_name
end

#objectObject (readonly) Also known as: o, obj, model, instance

Returns the value of attribute object



4
5
6
# File 'lib/binding.rb', line 4

def object
  @object
end

#optionsObject (readonly)

Returns the value of attribute options



4
5
6
# File 'lib/binding.rb', line 4

def options
  @options
end

#persisterObject (readonly)

Returns the value of attribute persister



4
5
6
# File 'lib/binding.rb', line 4

def persister
  @persister
end

#targetObject (readonly)

Returns the value of attribute target



4
5
6
# File 'lib/binding.rb', line 4

def target
  @target
end

#transitions(opts = {}) ⇒ Object (readonly)

transition validation



117
118
119
# File 'lib/binding.rb', line 117

def transitions
  @transitions
end

Instance Method Details

#==(other) ⇒ Object

let's be == (and hence ===) the current_state_name as a symbol. a nice little convenience.



260
261
262
263
264
265
266
# File 'lib/binding.rb', line 260

def == other
  if other.respond_to?( :to_sym ) && current_state
    current_state_name == other.to_sym || super( other )
  else
    super( other )
  end
end

#can_transition?(event, target = nil, *args) ⇒ Boolean

event_name? [target], *args

Returns:

  • (Boolean)


79
80
81
82
83
84
85
86
87
# File 'lib/binding.rb', line 79

def can_transition?(event, target=nil, *args)
  begin
    if t = find_transition(event, target, *args)
      t.valid?(*args)
    end
  rescue IllegalTransition, UnknownTarget
    nil
  end
end

#current_stateObject Also known as: now, state

the current State



47
48
49
# File 'lib/binding.rb', line 47

def current_state
  persister.current_state
end

#current_state_nameObject Also known as: name, state_name, to_sym

the name, as a Symbol, of the binding's current_state



54
55
56
57
58
59
60
# File 'lib/binding.rb', line 54

def current_state_name
  begin
    current_state.name.to_sym
  rescue NoMethodError
    nil
  end
end

#cycle(event_or_array = nil, *args, &block) ⇒ Object

if there is one possible cyclical event, return a transition there otherwise, maybe we got an event name as an argument?



201
202
203
204
205
206
207
208
# File 'lib/binding.rb', line 201

def cycle(event_or_array=nil, *args, &block)
  if event_or_array.nil?
    transitions.cyclic.with(*args, &block).singular ||
      transitions.cyclic.with(*args, &block).valid.singular
  else
    transitions.cyclic.with(*args, &block).find(event_or_array)
  end
end

#cycle!(event_or_array = nil, *args, &block) ⇒ Object

if there is a single possible cycle() transition, fire and return it otherwise raise an IllegalTransition



212
213
214
215
216
217
218
# File 'lib/binding.rb', line 212

def cycle!(event_or_array=nil, *args, &block )
  returning cycle(event_or_array, *args, &block ) do |t|
    raise TransitionNotFound.new( self, transitions.cyclic.with(*args,&block), "Cannot cycle! unless there is exactly one cyclic event") \
      if t.nil?
    t.fire!
  end
end

#cycle?(event_or_array = nil, *args) ⇒ Boolean

if there is one possible cyclical event, evaluate its requirements (true/false), else nil

Returns:

  • (Boolean)


222
223
224
225
226
# File 'lib/binding.rb', line 222

def cycle?(event_or_array=nil, *args )
  if t = cycle(event_or_array, *args )
    t.requirements_met?
  end
end

#eventsObject Also known as: events_from_current_state

returns a list of Events which can fire from the current_state



100
101
102
103
104
# File 'lib/binding.rb', line 100

def events
  machine.events.select do |e|
    e.can_transition_from? current_state
  end.extend EventArray
end

#find_transition(event, target = nil, *args) ⇒ Object

event_name [target], *args



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

def find_transition(event, target=nil, *args)
  target ||= args.last[:to].to_sym rescue nil
  query = transitions.for_event(event).to(target).with(*args)
  query.find || query.valid.singular || nil
end

#fire_transition!(event, target = nil, *args) ⇒ Object

event_name! [target], *args



91
92
93
# File 'lib/binding.rb', line 91

def fire_transition!(event, target=nil, *args)
  find_transition(event, target, *args).fire!
end

#inspectObject

display something sensible that doesn't take up the whole screen



248
249
250
251
252
253
254
255
256
# File 'lib/binding.rb', line 248

def inspect
  '<#' + self.class.to_s + ' ' +
    attrs = [[:current_state, state_name.inspect],
             [:object_type , @object.class],
             [:method_name , method_name.inspect],
             [:field_name  , field_name.inspect],
             [:machine     , machine.to_s]].
    map {|x| x.join('=') }.join( " " ) + '>'
end

#invalid_events(*args) ⇒ Object



133
134
135
# File 'lib/binding.rb', line 133

def invalid_events(*args)
  (events - valid_events(*args)).extend StateArray
end

#next!(*args, &block) ⇒ Object Also known as: next_transition!, next_event!, next_state!

if there is a next_transition, create, fire & return it otherwise raise an IllegalTransition



175
176
177
178
179
180
181
# File 'lib/binding.rb', line 175

def next!( *args, &block )
  if t = next_transition( *args, &block )
    t.fire!
  else
    raise TransitionNotFound.new( self, valid_transitions(*args), "Exactly 1 valid transition required.")
  end
end

#next?(*args, &block) ⇒ Boolean

if there is a next_transition, return true / false depending on whether its requirements are met otherwise, nil

Returns:

  • (Boolean)


189
190
191
192
193
# File 'lib/binding.rb', line 189

def next?( *args, &block )
  if t = next_transition( *args, &block )
    t.requirements_met?
  end
end

#next_event(*args) ⇒ Object

if there is exactly one event which is valid with the given optional arguments, return it



169
170
171
# File 'lib/binding.rb', line 169

def next_event( *args )
  transitions.with(*args, &block).next_event
end

#next_state(*args, &block) ⇒ Object

if there is exactly one state reachable via a transition which is valid with the given optional arguments, return it.



163
164
165
# File 'lib/binding.rb', line 163

def next_state(*args, &block)
  transitions.with(*args, &block).next_state
end

#next_statesObject

all states which can be reached from the current_state. Does not check transition requirements, etc.



109
110
111
# File 'lib/binding.rb', line 109

def next_states
  events.map(&:targets).compact.flatten.uniq.extend StateArray
end

#next_transition(*args, &block) ⇒ Object

if there is exactly one legal & valid transition which can be fired with the given (optional) arguments, return it.



152
153
154
# File 'lib/binding.rb', line 152

def next_transition( *args, &block )
  transitions.with(*args, &block).next
end

#next_transition_excluding_cycles(*args, &block) ⇒ Object

as above but ignoring any transitions whose origin and target are the same



157
158
159
# File 'lib/binding.rb', line 157

def next_transition_excluding_cycles( *args, &block )
  transitions.not_cyclic.with(*args, &block).next
end

#reloadObject

SPECME DOCME OR KILLME



275
276
277
278
279
280
281
# File 'lib/binding.rb', line 275

def reload()
  if persister.is_a?( Persistence::ActiveRecord )
    object.reload
  end
  persister.reload
  self
end

#singleton?Boolean

TODO better name is this a binding unique to a specific instance (not bound to a class)?

Returns:

  • (Boolean)


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

def singleton?
  options[:singleton]
end

#state_fu(name = nil) ⇒ Object

little kludge - allows the binding to reuse the same method definitions as 'object' in MethodFactory#method_definitions_for



296
297
298
# File 'lib/binding.rb', line 296

def state_fu(name=nil)
  self
end

#teleport!(target) ⇒ Object

change the current state of the binding without any requirements or other sanity checks, or any hooks firing. Useful for test / spec scenarios, and abusing the framework.



243
244
245
# File 'lib/binding.rb', line 243

def teleport!( target )
  persister.current_state=( machine.states[target] )
end

#transition(event_or_array, *args, &block) ⇒ Object

initializes a new Transition to the given destination, with the given *args (to be passed to requirements and hooks).

If a block is given, it yields the Transition or is executed in its evaluation context, depending on the arity of the block.



143
144
145
# File 'lib/binding.rb', line 143

def transition( event_or_array, *args, &block )
  return transitions.with(*args, &block).find(event_or_array)
end

#update!(*args, &block) ⇒ Object

next! without the raise if there's no next transition TODO SPECME



230
231
232
233
234
# File 'lib/binding.rb', line 230

def update!( *args, &block )
  if t = next_transition( *args, &block )
    t.fire!
  end
end

#valid_events(*args) ⇒ Object



129
130
131
# File 'lib/binding.rb', line 129

def valid_events(*args)
  valid_transitions(*args).events
end

#valid_next_states(*args) ⇒ Object



125
126
127
# File 'lib/binding.rb', line 125

def valid_next_states(*args)
  valid_transitions(*args).targets
end

#valid_transitions(*args) ⇒ Object



121
122
123
# File 'lib/binding.rb', line 121

def valid_transitions(*args)
  transitions.valid.with(*args)
end