davidlee-state-fu 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. data/LICENSE +40 -0
  2. data/README.textile +174 -0
  3. data/Rakefile +87 -0
  4. data/lib/no_stdout.rb +32 -0
  5. data/lib/state-fu.rb +93 -0
  6. data/lib/state_fu/binding.rb +262 -0
  7. data/lib/state_fu/core_ext.rb +23 -0
  8. data/lib/state_fu/event.rb +98 -0
  9. data/lib/state_fu/exceptions.rb +42 -0
  10. data/lib/state_fu/fu_space.rb +50 -0
  11. data/lib/state_fu/helper.rb +189 -0
  12. data/lib/state_fu/hooks.rb +28 -0
  13. data/lib/state_fu/interface.rb +139 -0
  14. data/lib/state_fu/lathe.rb +247 -0
  15. data/lib/state_fu/logger.rb +10 -0
  16. data/lib/state_fu/machine.rb +159 -0
  17. data/lib/state_fu/method_factory.rb +95 -0
  18. data/lib/state_fu/persistence/active_record.rb +27 -0
  19. data/lib/state_fu/persistence/attribute.rb +46 -0
  20. data/lib/state_fu/persistence/base.rb +98 -0
  21. data/lib/state_fu/persistence/session.rb +7 -0
  22. data/lib/state_fu/persistence.rb +50 -0
  23. data/lib/state_fu/sprocket.rb +27 -0
  24. data/lib/state_fu/state.rb +45 -0
  25. data/lib/state_fu/transition.rb +213 -0
  26. data/spec/helper.rb +86 -0
  27. data/spec/integration/active_record_persistence_spec.rb +189 -0
  28. data/spec/integration/class_accessor_spec.rb +127 -0
  29. data/spec/integration/event_definition_spec.rb +74 -0
  30. data/spec/integration/ex_machine_for_accounts_spec.rb +79 -0
  31. data/spec/integration/example_01_document_spec.rb +127 -0
  32. data/spec/integration/example_02_string_spec.rb +87 -0
  33. data/spec/integration/instance_accessor_spec.rb +100 -0
  34. data/spec/integration/machine_duplication_spec.rb +95 -0
  35. data/spec/integration/requirement_reflection_spec.rb +201 -0
  36. data/spec/integration/sanity_spec.rb +31 -0
  37. data/spec/integration/state_definition_spec.rb +177 -0
  38. data/spec/integration/transition_spec.rb +1060 -0
  39. data/spec/spec.opts +7 -0
  40. data/spec/units/binding_spec.rb +145 -0
  41. data/spec/units/event_spec.rb +232 -0
  42. data/spec/units/exceptions_spec.rb +75 -0
  43. data/spec/units/fu_space_spec.rb +95 -0
  44. data/spec/units/lathe_spec.rb +567 -0
  45. data/spec/units/machine_spec.rb +237 -0
  46. data/spec/units/method_factory_spec.rb +359 -0
  47. data/spec/units/sprocket_spec.rb +71 -0
  48. data/spec/units/state_spec.rb +50 -0
  49. metadata +122 -0
data/LICENSE ADDED
@@ -0,0 +1,40 @@
1
+ # State Fu
2
+ #
3
+ # The original master repository for State-Fu is at github:
4
+ # http://github.com/davidlee/state-fu
5
+ #
6
+ # Original Author: David Lee (2009)
7
+ # http://github.com/davidlee
8
+ #
9
+ # Thanks to: Ryan Allen - ryan-allen/workflow
10
+ # John Barnette - jbarnett/stateful
11
+ # Scott Barron - rubyist/aasm
12
+ #
13
+ # and other rubyists too numerous to mention, for the inspiration
14
+ #
15
+ # This software is released under this BSD license:
16
+ #
17
+ # Copyright (c) 2009, David Lee. All rights reserved.
18
+ #
19
+ # Redistribution and use in source and binary forms, with or without
20
+ # modification, are permitted provided that the following conditions
21
+ # are met:
22
+ #
23
+ # * Redistributions of source code must retain the above copyright
24
+ # notice, this list of conditions and the following disclaimer.
25
+ # * Redistributions in binary form must reproduce the above copyright
26
+ # notice, this list of conditions and the following disclaimer in the
27
+ # documentation and/or other materials provided with the distribution.
28
+ #
29
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
30
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
31
+ # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
32
+ # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
33
+ # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
34
+ # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
35
+ # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
36
+ # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
37
+ # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
38
+ # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
39
+ # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
40
+ # POSSIBILITY OF SUCH DAMAGE.
data/README.textile ADDED
@@ -0,0 +1,174 @@
1
+ h1. State-Fu
2
+
3
+ h2. What is it?
4
+
5
+ State-Fu is:
6
+
7
+ * an unique toolkit for state-oriented programming
8
+
9
+ * a rich DSL for describing workflows, rules engines and behaviour
10
+
11
+ * something you've probably wanted for a long time but didn't know it
12
+
13
+ It lets you describe:
14
+
15
+ * series of discrete states
16
+
17
+ * events which can change the current state
18
+
19
+ * rules about when these events can occur
20
+
21
+ * behaviours which occur when they do
22
+
23
+ Other libraries exist for ruby which do some or all of these
24
+ things. "What's different about State-Fu?", you may ask.
25
+
26
+ Those libraries you've played with are toys. They're made of
27
+ plastic. State-Fu is forged from a reassuringly dense but
28
+ unidentifiable metal which comes only from the rarest of meteorites,
29
+ and it ticks when you hold it up to your ear.[1]
30
+
31
+ State-Fu is elegant, powerful and transparent enough that you can use
32
+ it to drive substantial parts of your application, and actually want
33
+ to do so.
34
+
35
+ It is designed as a library for authors, as well as users, of
36
+ libraries: State-Fu goes to great lengths to impose very few limits on
37
+ your ability to introspect, manipulate and extend the core features.
38
+
39
+ It is also delightfully elegant and easy to use for simple things:
40
+
41
+ <pre><code>
42
+
43
+ class Document < ActiveRecord::Base
44
+ include StateFu
45
+
46
+ def update_rss
47
+ puts "new feed!"
48
+ # ... do something here
49
+ end
50
+
51
+ machine( :status ) do
52
+ state :draft do
53
+ event :publish, :to => :published
54
+ end
55
+
56
+ state :published do
57
+ on_entry :update_rss
58
+ requires :author # a database column
59
+ end
60
+
61
+ event :delete, :from => :ALL, :to => :deleted do
62
+ execute :destroy
63
+ end
64
+ end
65
+ end
66
+
67
+ my_doc = Document.new
68
+
69
+ my_doc.status # returns a StateFu::Binding, which lets us access the 'Fu
70
+ my_doc.status.state => 'draft' # if this wasn't already a database column or attribute, an
71
+ # attribute has been created to keep track of the state
72
+ my_doc.status.name => :draft # the name of the current_state (defaults to the first defined)
73
+ my_doc.status.publish! # raised => StateFu::RequirementError: [:author]
74
+ # the author requirement prevented the transition
75
+ my_doc.status.name => :draft # see? still a draft.
76
+ my_doc.author = "Susan" # so let's satisfy it ...
77
+ my_doc.publish! # and try again.
78
+ "new feed!" # aha - our event hook fires!
79
+ my_doc.status.name => :published # and the state has been updated.
80
+
81
+ </code></pre>
82
+
83
+ A few of the features which set State-Fu apart for more ambitious work are:
84
+
85
+ * a lovely, simple and flexible API gives you plenty of choices
86
+
87
+ * use an ActiveRecord field for state persistence, or just an
88
+ attribute - or use both, on the same class, for different workflows
89
+
90
+ * customising the persistence mechanism (eg to use a Rails session,
91
+ or a text file) is as easy as defining a getter and setter method
92
+
93
+ * define any number of workflows on the same object / model, or re-use
94
+ them across multiple classes
95
+
96
+ * events can transition from / to any number of states
97
+
98
+ * drive application behaviour with a rich set of event hooks
99
+
100
+ * define behaviour as methods on your objects, or keep it all in the
101
+ state machine itself
102
+
103
+ * requirements determine at runtime whether a particular state
104
+ transition can occur, and if not, can tell a user what they must do
105
+ to satisfy the requirements.
106
+
107
+ * requirement failure messages can be generated at runtime, making
108
+ use of whatever application and state-machine context they need
109
+
110
+ * transitions can be halted mid-execution, and you can actually
111
+ determine why, and where from
112
+
113
+ * in every event hook, requirement filter, and other method calls,
114
+ you have complete and tidy access to your classes, the state
115
+ machine, and the transition context. Use real ruby code anywhere,
116
+ without breathing through a straw!
117
+
118
+ * extend State-Fu with helper modules, or raw blocks of ruby code, to
119
+ model your problem domain - globally, per state machine / workflow,
120
+ or for an individual event transition
121
+
122
+ * store arbitrary meta-data on any component of State-Fu - a simple
123
+ but extremely powerful tool for integration
124
+
125
+ * designed for transparency, introspection and ease of debugging,
126
+ which means a dynamic, powerful system you can actually use without
127
+ headaches
128
+
129
+ * fast, lightweight and useful enough to use in any ruby
130
+ project - works with Rails but does not require it.
131
+
132
+ State-Fu works with any modern Ruby ( 1.8.6, 1.8.7, and 1.9.1)
133
+
134
+ fn1. No disrespect intended to the authors of other similar libraries
135
+ - some of whom I've borrowed an idea or two, and some useful criticism,
136
+ from. They're stand-up guys, all of them. It's the truth though.
137
+
138
+ I'd like to thank Ryan Allen in particular for his Workflow library,
139
+ which I previously forked, piled hundreds of lines of code into and renamed
140
+ Stateful (now deprecated). Some of his ideas (for example the ability to store metadata
141
+ easily on *everything* ) have been instrumental in State-Fu's design.
142
+
143
+ I'd also like to tip my hat at John Barnette, who's own
144
+ (coincidentally named) Stateful set a very high standard with an
145
+ exceptionally elegant API.
146
+
147
+ h2. Getting started
148
+
149
+ First up:
150
+
151
+ <pre>
152
+ <code>
153
+ sudo gem install rspec rr activesupport
154
+ rake
155
+ rake spec:doc # generate specdocs
156
+ rake doc # generate rdoc
157
+ rake gem # build the gem
158
+ rake gem:install # install it
159
+ </code>
160
+ </pre>
161
+ And have a peek in the specs folder or rdoc for usage / documentation.
162
+
163
+ Now you can simply:
164
+ <code>
165
+ require 'state-fu'
166
+ </code>
167
+ and
168
+ <code>
169
+ include StateFu
170
+ </code> in any class you wish to make stateful.
171
+
172
+ If you have questions, feature request or ideas, please join the "google group":http://groups.google.com/group/state-fu
173
+
174
+ Also see the "issue tracker":http://github.com/davidlee/state-fu/issues
data/Rakefile ADDED
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/ruby1.9
2
+ require "spec/rake/spectask"
3
+ #require 'cucumber/rake/task'
4
+ require "date"
5
+ require "fileutils"
6
+ require "rubygems"
7
+
8
+ module Rakefile
9
+ def self.windows?
10
+ /djgpp|(cyg|ms|bcc)win|mingw/ =~ RUBY_PLATFORM
11
+ end
12
+ end
13
+
14
+ # to build the gem:
15
+ #
16
+ # gem install jeweller
17
+ # rake build
18
+ # rake install
19
+ begin
20
+ require 'jeweler'
21
+ Jeweler::Tasks.new do |s| # gemspec (Gem::Specification)
22
+ s.name = "state-fu"
23
+ s.rubyforge_project = "state-fu"
24
+ s.platform = Gem::Platform::RUBY
25
+ s.has_rdoc = true
26
+ # s.extra_rdoc_files = ["README.rdoc"]
27
+ s.summary = "A rich library for state-oriented programming with state machines / workflows"
28
+ s.description = s.summary
29
+ s.author = "David Lee"
30
+ s.email = "david@rubyist.net.au"
31
+ s.homepage = "http://github.com/davidlee/state-fu"
32
+ s.require_path = "lib"
33
+ # s.files = %w(README.rdoc Rakefile) + Dir.glob("{lib,spec}/**/*")
34
+ s.files = %w(Rakefile) + Dir.glob("{lib,spec}/**/*")
35
+ end
36
+ rescue LoadError
37
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
38
+ end
39
+
40
+ namespace :spec do
41
+ desc "Run both units and integration specs"
42
+ task :both => [:units, :integration]
43
+
44
+ desc "Run all specs"
45
+ Spec::Rake::SpecTask.new(:all) do |t|
46
+ t.spec_files = FileList["spec/**/*_spec.rb"]
47
+ t.spec_opts = ["--options", "spec/spec.opts"]
48
+ end
49
+
50
+ desc "Run unit specs"
51
+ Spec::Rake::SpecTask.new(:units) do |t|
52
+ t.spec_files = FileList["spec/units/*_spec.rb"]
53
+ t.spec_opts = ["--options", "spec/spec.opts"]
54
+ end
55
+ task :unit => :units
56
+
57
+ desc "Run integration specs"
58
+ Spec::Rake::SpecTask.new(:integration) do |t|
59
+ t.spec_files = FileList["spec/integration/*_spec.rb"]
60
+ t.spec_opts = ["--options", "spec/spec.opts"]
61
+ end
62
+ task :system => :integration
63
+
64
+ desc "Print Specdoc for all specs (eaxcluding plugin specs)"
65
+ Spec::Rake::SpecTask.new(:doc) do |t|
66
+ t.spec_files = FileList["spec/**/*_spec.rb"]
67
+ t.spec_opts = ["--format", "nested","--backtrace","--color"]
68
+ end
69
+
70
+ desc "Run autotest"
71
+ task :auto do |t|
72
+ exec 'autospec'
73
+ end
74
+
75
+ end
76
+
77
+ desc 'Runs irb in this project\'s context'
78
+ task :irb do |t|
79
+ exec 'irb -I lib -r state-fu'
80
+ end
81
+
82
+ desc 'Runs rdoc on the project lib directory'
83
+ task :doc do |t|
84
+ exec 'rdoc lib/'
85
+ end
86
+
87
+ task :default => 'spec:all'
data/lib/no_stdout.rb ADDED
@@ -0,0 +1,32 @@
1
+ require 'stringio'
2
+
3
+ module NoStdout
4
+ module InstanceMethods
5
+
6
+ def no_stdout ( to = StringIO.new('','r+'), &block )
7
+ # supply an IO of your own to capture STDOUT, otherwise it's put in a StringIO
8
+ orig_stdout = $stdout
9
+ $stdout = @alt_stdout = to
10
+ result = yield
11
+ $stdout = orig_stdout
12
+ result
13
+ end
14
+
15
+ def last_stdout
16
+ return nil unless @alt_stdout
17
+ @alt_stdout.rewind
18
+ @alt_stdout.read
19
+ end
20
+
21
+ end
22
+
23
+ # TODO - explain / remember why this has two class_eval blocks -
24
+ # should one be an extend?
25
+ def self.included klass
26
+ klass.class_eval do
27
+ include InstanceMethods
28
+ end
29
+ klass.extend InstanceMethods
30
+ end
31
+
32
+ end
data/lib/state-fu.rb ADDED
@@ -0,0 +1,93 @@
1
+
2
+ #!/usr/bin/env ruby
3
+ #
4
+ # State-Fu
5
+ #
6
+ # State-Fu is a framework for state-oriented programming in ruby.
7
+ #
8
+ # You can use it to define state machines, workflows, rules engines,
9
+ # and the behaviours which relate to states and transitions between
10
+ # them.
11
+ #
12
+ # It is powerful and flexible enough to drive entire applications, or
13
+ # substantial parts of them. It is designed as a library for authors,
14
+ # as well as users, of libraries: State-Fu goes to great lengths to
15
+ # impose very few limits on your ability to introspect, manipulate and
16
+ # extend the core features.
17
+ #
18
+ # It is also delightfully elegant and easy to use for simple things:
19
+ #
20
+ # class Document < ActiveRecord::Base
21
+ # include StateFu
22
+ #
23
+ # def update_rss
24
+ # puts "new feed!"
25
+ # # ... do something here
26
+ # end
27
+ #
28
+ # machine( :status ) do
29
+ # state :draft do
30
+ # event :publish, :to => :published
31
+ # end
32
+ #
33
+ # state :published do
34
+ # on_entry :update_rss
35
+ # requires :author # a database column
36
+ # end
37
+ #
38
+ # event :delete, :from => :ALL, :to => :deleted do
39
+ # execute :destroy
40
+ # end
41
+ # end
42
+ # end
43
+ #
44
+ # my_doc = Document.new
45
+ #
46
+ # my_doc.status # returns a StateFu::Binding, which lets us access the 'Fu
47
+ # my_doc.status_state => 'draft' # if this wasn't already a database column or attribute, an
48
+ # # attribute has been created to keep track of the state
49
+ # my_doc.status.name => :draft # the name of the current_state (defaults to the first defined)
50
+ # my_doc.status.publish! # raised => StateFu::RequirementError: [:author]
51
+ # # the author requirement prevented the transition
52
+ # my_doc.status.name => :draft # see? still a draft.
53
+ # my_doc.author = "Susan" # so let's satisfy it ...
54
+ # my_doc.publish! # and try again.
55
+ # "new feed!" # aha - our event hook fires!
56
+ # my_doc.status.name => :published # and the state has been updated.
57
+
58
+ require 'activesupport'
59
+
60
+ require 'state_fu/core_ext'
61
+ require 'state_fu/logger'
62
+ require 'state_fu/helper'
63
+ require 'state_fu/exceptions'
64
+ require 'state_fu/fu_space'
65
+ require 'state_fu/machine'
66
+ require 'state_fu/lathe'
67
+ require 'state_fu/method_factory'
68
+ require 'state_fu/binding'
69
+ require 'state_fu/persistence'
70
+ require 'state_fu/persistence/base'
71
+ require 'state_fu/persistence/active_record'
72
+ require 'state_fu/persistence/attribute'
73
+ require 'state_fu/sprocket'
74
+ require 'state_fu/state'
75
+ require 'state_fu/event'
76
+ require 'state_fu/hooks'
77
+ require 'state_fu/interface'
78
+ require 'state_fu/transition'
79
+
80
+ module StateFu
81
+ DEFAULT_MACHINE = :state_fu
82
+
83
+ def self.included( klass )
84
+ klass.extend( Interface::ClassMethods )
85
+ klass.send( :include, Interface::InstanceMethods )
86
+ end
87
+ end
88
+
89
+ if __FILE__ == $0
90
+ # run rake stuff (specs / doc )
91
+ # load example_machine.rb
92
+ # drop into irb
93
+ end
@@ -0,0 +1,262 @@
1
+ module StateFu
2
+ class Binding
3
+ include ContextualEval
4
+
5
+ attr_reader :object, :machine, :method_name, :persister, :transitions, :options
6
+
7
+ def initialize( machine, object, method_name, options={} )
8
+ @machine = machine
9
+ @object = object
10
+ @method_name = method_name
11
+ @transitions = []
12
+ @options = options.symbolize_keys!
13
+ field_name = StateFu::FuSpace.field_names[object.class][@method_name]
14
+ raise( ArgumentError, "No field_name" ) unless field_name
15
+ # ensure state field is set up (in case we created this binding
16
+ # manually, instead of via Machine.bind!)
17
+ StateFu::Persistence.prepare_field( object.class, field_name )
18
+ # add a persister
19
+ @persister = StateFu::Persistence.for( self, field_name )
20
+ Logger.info( "Persister added: #@persister ")
21
+
22
+ # define event methods on self( binding ) and @object
23
+ StateFu::MethodFactory.new( self ).install!
24
+
25
+ # StateFu::Persistence.prepare_field( @object.class, field_name )
26
+ end
27
+ alias_method :o, :object
28
+ alias_method :obj, :object
29
+ alias_method :model, :object
30
+ alias_method :instance, :object
31
+
32
+ alias_method :machine, :machine
33
+ alias_method :workflow, :machine
34
+ alias_method :state_machine, :machine
35
+
36
+ def field_name
37
+ persister.field_name
38
+ end
39
+
40
+ def current_state
41
+ persister.current_state
42
+ end
43
+ alias_method :at, :current_state
44
+ alias_method :now, :current_state
45
+ alias_method :state, :current_state
46
+
47
+ def current_state_name
48
+ current_state.name
49
+ end
50
+ alias_method :name, :current_state_name
51
+ alias_method :state_name, :current_state_name
52
+ alias_method :to_sym, :current_state_name
53
+
54
+ # a list of events which can fire from the current_state
55
+ def events
56
+ machine.events.select {|e| e.complete? && e.from?( current_state ) }.extend EventArray
57
+ end
58
+ alias_method :events_from_current_state, :events
59
+
60
+ # the subset of events() whose requirements for firing are met
61
+ def valid_events
62
+ return nil unless current_state
63
+ return [] unless current_state.exitable_by?( self )
64
+ events.select {|e| e.fireable_by?( self ) }.extend EventArray
65
+ end
66
+
67
+ def invalid_events
68
+ (events - valid_events).extend StateArray
69
+ end
70
+
71
+ def unmet_requirements_for(event, target)
72
+ raise NotImplementedError
73
+ end
74
+
75
+ # the counterpart to valid_events - the states we can arrive at in
76
+ # the firing of one event, taking into account event and state
77
+ # transition requirements
78
+ def valid_next_states
79
+ valid_transitions.values.flatten.uniq.extend StateArray
80
+ end
81
+
82
+ def next_states
83
+ events.map(&:targets).compact.flatten.uniq.extend StateArray
84
+ end
85
+
86
+ def invalid_next_states
87
+ states - valid_states
88
+ end
89
+
90
+ # returns a hash of { event => [states] } whose transition
91
+ # requirements are met
92
+ def valid_transitions
93
+ h = {}
94
+ return nil if valid_events.nil?
95
+ valid_events.each do |e|
96
+ h[e] = e.targets.select do |s|
97
+ s.enterable_by?( self )
98
+ end
99
+ end
100
+ h
101
+ end
102
+
103
+ # initialize a new transition
104
+ def transition( event_or_array, *args, &block )
105
+ event, target = parse_destination( event_or_array )
106
+ StateFu::Transition.new( self, event, target, *args, &block )
107
+ end
108
+ alias_method :fire, :transition
109
+ alias_method :trigger, :transition
110
+
111
+ # sanitize args for fire! and fireable?
112
+ def parse_destination( event_or_array )
113
+ case event_or_array
114
+ when StateFu::Event, Symbol
115
+ event = event_or_array
116
+ target = nil
117
+ when Array
118
+ event, target = *event_or_array
119
+ end
120
+ x = event_or_array.is_a?( Array ) ? event_or_array.map(&:class) : event_or_array
121
+ raise ArgumentError.new( x.inspect ) unless
122
+ [StateFu::Event, Symbol ].include?( event.class ) &&
123
+ [StateFu::State, Symbol, NilClass].include?( target.class )
124
+ [event, target]
125
+ end
126
+
127
+ # check that the event and target are "valid" (all requirements met)
128
+ def fireable?( event_or_array )
129
+ event, target = parse_destination( event_or_array )
130
+ begin
131
+ t = transition( [event, target] )
132
+ !! t.requirements_met?
133
+ rescue InvalidTransition => e
134
+ nil
135
+ end
136
+ end
137
+ alias_method :event?, :fireable?
138
+ alias_method :trigger?, :fireable?
139
+ alias_method :triggerable?, :fireable?
140
+ alias_method :transition?, :fireable?
141
+ alias_method :transitionable?,:fireable?
142
+
143
+ # construct an event transition and fire it
144
+ def fire!( event_or_array, *args, &block)
145
+ event, target = parse_destination( event_or_array )
146
+ t = transition( [event, target], *args, &block )
147
+ t.fire!
148
+ t
149
+ end
150
+ alias_method :event!, :fire!
151
+ alias_method :trigger!, :fire!
152
+ alias_method :transition!, :fire!
153
+
154
+ # evaluate a requirement depending whether it's a method or proc,
155
+ # and its arity - see helper.rb (ContextualEval) for the smarts
156
+ def evaluate_requirement( name )
157
+ evaluate_named_proc_or_method( name )
158
+ end
159
+
160
+ def evaluate_requirement_message( name, dest )
161
+ msg = machine.requirement_messages[name]
162
+ if [String, NilClass].include?( msg.class )
163
+ return msg
164
+ else
165
+ if dest.is_a?( StateFu::Transition )
166
+ t = dest
167
+ else
168
+ event, target = parse_destination( event_or_array )
169
+ t = transition( event, target )
170
+ end
171
+ case msg
172
+ when Symbol
173
+ t.evaluate_named_proc_or_method( msg )
174
+ when Proc
175
+ t.evaluate &msg
176
+ end
177
+ end
178
+ end
179
+
180
+ # if there is one simple event, return a transition for it
181
+ # else return nil
182
+ # TODO - not convinced about the method name / aliases - but next
183
+ # is reserved :/
184
+ def next_transition( *args, &block )
185
+ return nil if valid_transitions.nil?
186
+ next_transition_candidates = valid_transitions.select {|e, s| s.length == 1 }
187
+ if next_transition_candidates.length == 1
188
+ nt = next_transition_candidates.first
189
+ evt = nt[0]
190
+ targ = nt[1][0]
191
+ return transition( [ evt, targ], *args, &block )
192
+ end
193
+ end
194
+
195
+ def next_state
196
+ next_transition && next_transition.target
197
+ end
198
+
199
+ alias_method :next_event, :next_transition
200
+
201
+ # if there is a next_transition, create, fire & return it
202
+ # otherwise raise an InvalidTransition
203
+ def next!( *args, &block )
204
+ if t = next_transition( *args, &block )
205
+ t.fire!
206
+ t
207
+ else
208
+ n = valid_transitions && valid_transitions.length
209
+ raise InvalidTransition.
210
+ new( self, current_state, valid_transitions,
211
+ "there are #{n} candidate transitions, need exactly 1")
212
+ end
213
+ end
214
+ alias_method :next_state!, :next!
215
+ alias_method :next_event!, :next!
216
+
217
+ # if there is a next_transition, return true / false depending on
218
+ # whether its requirements are met
219
+ # otherwise, nil
220
+ def next?( *args, &block )
221
+ if t = next_transition( *args, &block )
222
+ t.requirements_met?
223
+ end
224
+ end
225
+ alias_method :next_state?, :next?
226
+ alias_method :next_event?, :next?
227
+
228
+ # if there is one possible cyclical event, return a transition there
229
+ def cycle( *args, &block)
230
+ cycle_events = events.select {|e| e.target == current_state }
231
+ if cycle_events.length == 1
232
+ transition( cycle_events[0], *args, &block )
233
+ end
234
+ end
235
+
236
+ # if there is a cycle() transition, fire and return it
237
+ # otherwise raise an InvalidTransition
238
+ def cycle!( *args, &block )
239
+ if t = cycle( *args, &block )
240
+ t.fire!
241
+ t
242
+ else
243
+ err_msg = "Cannot cycle! unless there is exactly one event leading from the current state to itself"
244
+ raise InvalidTransition.new( self, current_state, current_state, err_msg )
245
+ end
246
+ end
247
+
248
+ # if there is one possible cyclical event, evaluate its
249
+ # requirements (true/false), else nil
250
+ def cycle?
251
+ if t = cycle
252
+ t.requirements_met?
253
+ end
254
+ end
255
+
256
+ # display something sensible that doesn't take up the whole screen
257
+ def inspect
258
+ "#<#{self.class} ##{__id__} object_type=#{@object.class} method_name=#{method_name.inspect} field_name=#{persister.field_name.inspect} machine=#{@machine.inspect} options=#{options.inspect}>"
259
+ end
260
+
261
+ end
262
+ end