Class: StateFu::Machine

Inherits:
Object show all
Includes:
HasOptions, Applicable
Defined in:
lib/machine.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from HasOptions

#[], #[]=, included

Methods included from Applicable

included

Constructor Details

#initialize(options = {}, &block) ⇒ Machine


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

def initialize( options={}, &block )
  @states               = [].extend( StateArray  )
  @events               = [].extend( EventArray  )
  @helpers              = [].extend( HelperArray )
  @tools                = [].extend( ToolArray   )
  @named_procs          = {}
  @requirement_messages = {}
  @options              = options
  @hooks                = Hooks.for( self )
  apply!( &block ) if block_given?
end

Instance Attribute Details

#eventsObject (readonly)

Instance Methods


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

def events
  @events
end

#helpersObject (readonly)

Instance Methods


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

def helpers
  @helpers
end

#hooksObject (readonly)

Returns the value of attribute hooks


11
12
13
# File 'lib/machine.rb', line 11

def hooks
  @hooks
end

#named_procsObject (readonly)

Instance Methods


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

def named_procs
  @named_procs
end

#optionsObject (readonly)

Instance Methods


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

def options
  @options
end

#requirement_messagesObject (readonly)

Instance Methods


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

def requirement_messages
  @requirement_messages
end

#statesObject (readonly)

Instance Methods


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

def states
  @states
end

#toolsObject (readonly)

Instance Methods


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

def tools
  @tools
end

Class Method Details

.bind!(machine, owner, name, options = {}) ⇒ Object

make it so that a class which has included StateFu has a binding to this machine


32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/machine.rb', line 32

def self.bind!(machine, owner, name, options={})
  name = name.to_sym
  options[:define_methods] = (name == DEFAULT) unless options.symbolize_keys!.has_key?(:define_methods)
  options[:field_name] ||= Persistence.default_field_name(name)
  options[:singleton] = true unless owner.is_a?(Class)
  if options[:singleton]
    _binding = StateFu::Binding.new machine, owner, name, options

    # define an accessor method with the given name
    MethodFactory.define_singleton_method(owner, name) { _binding }
    if alias_name = options[:alias] || options[:as]
      MethodFactory.define_singleton_method(owner, alias_name) { _binding }
    end
  else
    klass = owner
    klass.state_fu_machines[name] = machine
    klass.state_fu_options[name]  = options

    # prepare the state machine accessor method
    if owner.respond_to? name
      raise "FIXME " + name
    else
      klass.class_eval do
        define_method name do
          state_fu name
        end        # allow aliases to be set up, e.g. machine(:as => :status)

        if alias_name = options[:alias] || options[:as]
          alias_method alias_name, name
        end
      end
    end

    # prepare event / state class methods
    StateFu::MethodFactory.prepare_class_machine klass, machine, name, options

    # prepare the persistence field
    StateFu::Persistence.prepare_field owner, options[:field_name]
  end
end

.BINDINGSObject


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

def self.BINDINGS
  @@_bindings ||= {}
end

.for_class(klass, name, options = {}, &block) ⇒ Object

Class methods


17
18
19
20
21
22
23
24
25
26
27
28
# File 'lib/machine.rb', line 17

def self.for_class(klass, name, options={}, &block)
  options.symbolize_keys!
  name = name.to_sym
  unless machine = klass.state_fu_machines[ name ]
    machine = new(options)
  end
  if block_given?
    machine.apply! &block
  end
  machine.bind! klass, name, options
  machine
end

.load_yaml(yaml) ⇒ Object


204
205
206
# File 'lib/machine.rb', line 204

def self.load_yaml(yaml)
  StateFu::Blueprint.load_yaml(yaml)
end

Instance Method Details

#apply!(&block) ⇒ Object Also known as: lathe

merge the commands in &block with the existing machine; returns a lathe for the machine.


93
94
95
# File 'lib/machine.rb', line 93

def apply!( &block )
  StateFu::Lathe.new( self, &block )
end

#bind!(owner, name = DEFAULT, options = {}) ⇒ Object

make it so a class which has included StateFu has a binding to this machine


132
133
134
# File 'lib/machine.rb', line 132

def bind!(owner, name=DEFAULT, options={})
  self.class.bind!(self, owner, name, options)
end

#deep_cloneObject Also known as: deep_copy

Marshal, the poor man's X-Ray photocopier. TODO: a version which will not break its teeth on procs


182
183
184
# File 'lib/machine.rb', line 182

def deep_clone
  Marshal::load(Marshal.dump(self))
end

#empty?Boolean


136
137
138
# File 'lib/machine.rb', line 136

def empty?
  states.empty?
end

#event_namesObject


161
162
163
# File 'lib/machine.rb', line 161

def event_names
  events.map(&:name)
end

#find_or_create_states_by_name(*args) ⇒ Object

given a messy bunch of symbols, find or create a list of matching States.

Raises:

  • (ArgumentError)

167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/machine.rb', line 167

def find_or_create_states_by_name( *args )
  args = args.compact.flatten
  raise ArgumentError.new( args.inspect ) unless args.all? { |a| [Symbol, StateFu::State].include? a.class }
  args.map do |s|
    unless state = states[s.to_sym]      # TODO clean this line up

      state = s.is_a?( StateFu::State ) ? s : StateFu::State.new( self, s )
      self.states << state
    end
    state
  end
end

#graphvizObject


191
192
193
# File 'lib/machine.rb', line 191

def graphviz
  @graphviz ||= Plotter.new(self).output
end

#helper(*modules_to_add) ⇒ Object

the modules listed here will be mixed into Binding and Transition objects for this machine. use this to define methods, references or data useful to you during transitions, event hooks, or in general use of StateFu.

They can be supplied as a string/symbol (as per rails controller helpers), or a Module.

To do this globally, just duck-punch StateFu::Machine / StateFu::Binding.


120
121
122
# File 'lib/machine.rb', line 120

def helper *modules_to_add
  modules_to_add.each { |mod| helpers << mod }
end

#helper_modulesObject


98
99
100
# File 'lib/machine.rb', line 98

def helper_modules
  helpers.modules
end

#initial_stateObject


153
154
155
# File 'lib/machine.rb', line 153

def initial_state()
  @initial_state ||= states.first
end

#initial_state=(s) ⇒ Object


140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/machine.rb', line 140

def initial_state=( s )
  case s
  when Symbol, String, StateFu::State
    unless init_state = states[ s.to_sym ]
      init_state = StateFu::State.new( self, s.to_sym )
      states << init_state
    end
    @initial_state = init_state
  else
    raise( ArgumentError, s.inspect )
  end
end

#inject_helpers_into(obj) ⇒ Object


102
103
104
# File 'lib/machine.rb', line 102

def inject_helpers_into( obj )
  helpers.inject_into( obj )
end

#inject_tools_into(obj) ⇒ Object


106
107
108
# File 'lib/machine.rb', line 106

def inject_tools_into( obj )
  tools.inject_into( obj )
end

#inspectObject


187
188
189
# File 'lib/machine.rb', line 187

def inspect
  "#<#{self.class} ##{__id__} states=#{state_names.inspect} events=#{event_names.inspect} options=#{options.inspect}>"
end

#serializable?Boolean

TODO simplify this by adding serializable? to state & event


200
201
202
# File 'lib/machine.rb', line 200

def serializable?
  named_procs.empty? && (states + events).all?(&:serializable?) 
end

#state_namesObject


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

def state_names
  states.map(&:name)
end

#to_yamlObject


195
196
197
# File 'lib/machine.rb', line 195

def to_yaml
  StateFu::Blueprint.to_yaml(self)
end

#tool(*modules_to_add) ⇒ Object

same as helper, but for extending Lathes rather than the Bindings / Transitions. use this to extend the Lathe DSL to suit your problem domain.


126
127
128
# File 'lib/machine.rb', line 126

def tool *modules_to_add
  modules_to_add.each { |mod| tools << mod }
end