ruby-state-machine 0.0.3 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 665d443a898a9f324aa6c3940c99287be592a92a
4
+ data.tar.gz: dad2a4f3bda51408aae0a0dcb9d456052bc7ee6f
5
+ SHA512:
6
+ metadata.gz: e42f12cfd35f6b1e787627262d4ec3d1776bc0e6e412ac1513c883e21389cc677b7c023a4a9613869cc2ca0a8623b08ab4b3b3acf9a3ca87685031638a4f9ff2
7
+ data.tar.gz: 5c709191cdab80cc7b21ee2eaecb5d66d33194fea49eecb2d5e584a83baf1aded13896b528fe771aefa04e862ae1eadb46eba0922cade197ecc01fa0833ebf3d
@@ -0,0 +1,86 @@
1
+ ## Ruby State Machine
2
+ Ruby State Machine (ruby-state-machine) is a full-featured state machine gem for use within ruby. It can also be used in Rails. This was written because we required a state machine that allowed different actions to be performed based on the previous and current events, as well as injecting logic (a "decider") to determine the next event.
3
+
4
+ ## Installation:
5
+
6
+ Add this line to your application's Gemfile:
7
+
8
+ gem 'ruby-state-machine'
9
+
10
+ And then execute:
11
+
12
+ $ bundle
13
+
14
+ Or install it yourself as:
15
+
16
+ $ gem install ruby-state-machine
17
+
18
+ ## Location:
19
+ Here: http://github.com/tangledpath/ruby-state-machine
20
+
21
+ RDocs: http://ruby-state-mach.rubyforge.org/
22
+
23
+
24
+ ## USAGE:
25
+
26
+ ```ruby
27
+ require 'ruby-state-machine/state_machine'
28
+
29
+ # Note, a state machine is not created directly; instead, the behavior of a state
30
+ # machine is added through a mixin, e.g.:
31
+ class SampleMachine
32
+ include StateMachine
33
+ state_machine :states => [:a_state, :b_state, :c_state, :d_state], :events => [:w_event, :x_event, :y_event, :z_event]
34
+ state_transition :state=>:a_state, :event=>:x_event, :next=>:c_state # Define next state for :a_state when :x_event is sent
35
+ state_transition :state=>:a_state, :event=>:y_event, :next=>:a_state # Define next state for :a_state when :y_event is sent
36
+ state_transition :state=>:a_state, :event=>:z_event, :next=>:b_state # ...
37
+
38
+ state_transition :state=>:b_state, :event=>:w_event, :next=>:b_state
39
+ state_transition :state=>:b_state, :event=>:y_event, :next=>:c_state
40
+ state_transition :state=>:b_state, :event=>:z_event, :next=>:a_state
41
+
42
+ state_transition :state=>:c_state, :event=>:x_event, :next=>:b_state
43
+ end
44
+
45
+ sm = SampleMachine.new
46
+ puts sm.current_state # :a_state
47
+ sm.send_event(:x_event)
48
+ puts sm.current_state # :c_state
49
+ ```
50
+
51
+ For examples of other functionality, including branching, deciders, lambdas, etc, see http://ruby-state-mach.rubyforge.org/StateMachine/ClassMethods.html#state_transition-instance_method.
52
+
53
+
54
+ ## CONTRIBUTE
55
+
56
+ 1. Fork it
57
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
58
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
59
+ 4. Push to the branch (`git push origin my-new-feature`)
60
+ 5. Create new Pull Request
61
+
62
+ ## LICENSE:
63
+
64
+ (The MIT License)
65
+
66
+ Copyright (c) 2007-2013 Steven Miers
67
+
68
+ Permission is hereby granted, free of charge, to any person obtaining
69
+ a copy of this software and associated documentation files (the
70
+ 'Software'), to deal in the Software without restriction, including
71
+ without limitation the rights to use, copy, modify, merge, publish,
72
+ distribute, sublicense, and/or sell copies of the Software, and to
73
+ permit persons to whom the Software is furnished to do so, subject to
74
+ the following conditions:
75
+
76
+ The above copyright notice and this permission notice shall be
77
+ included in all copies or substantial portions of the Software.
78
+
79
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
80
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
81
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
82
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
83
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
84
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
85
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
86
+
@@ -1,6 +1,6 @@
1
- $:.unshift(File.dirname(__FILE__)) unless
2
- $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
1
+ require "ruby-state-machine/version"
2
+ #require "ruby-state-machine/state_machine"
3
3
 
4
4
  module RubyStateMachine
5
- VERSION = '0.0.3'
5
+
6
6
  end
@@ -0,0 +1,8 @@
1
+ class BoundedArray < Array
2
+ attr_accessor :bounded_size
3
+ def push(args)
4
+ super
5
+ @bounded_size ||= 10
6
+ shift until size <= bounded_size
7
+ end
8
+ end
@@ -0,0 +1,252 @@
1
+ require 'ruby-state-machine/bounded_array'
2
+
3
+ # StateMachine
4
+ # Simple and flexible state machine. Define a state machine with an array of states,
5
+ # an array of events, and one or more transition actions. A transition can be
6
+ # as simple as the symbol for the next state, a lambda (code) to execute. Primitive
7
+ # branching can also be achieved if necessary by using a "decider" instance method.
8
+ #
9
+ # The {StateMachineTest} (view source) also contains examples and unit tests for most (if not all)
10
+ # of the available functionality.
11
+ # Also see {http://ruby-state-mach.rubyforge.org/ README} for examples
12
+ # @see ClassMethods#state_transition StateTransition for full details on the variations available.
13
+ # @see StateMachineTest
14
+ module StateMachine
15
+ def self.included(base)
16
+ base.extend StateMachine::ClassMethods
17
+ end
18
+
19
+
20
+ module ClassMethods
21
+ attr_accessor :machine
22
+
23
+ # Create state machine with the following options:
24
+ # states Array[symbols], each symbol is name of state
25
+ # events Array[symbols], each symbol is name of event
26
+ # default_state symbol, symbol is one of states
27
+ def state_machine(opts)
28
+ @machine = StateMachine::Machine.new(opts)
29
+ self.send(:include, StateMachine::InstanceMethods)
30
+ end
31
+
32
+ # Add a transition to state machine with the following keys:
33
+ # :state=>state (symbol) for which transition is being declared
34
+ # :event=>event (symbol) which causes transition
35
+ # :decider=>name of instance method on state machine specialization that "decides"
36
+ # next state in case of multiple actions for next state (see below).
37
+ # Method in question should return name of next state, or name of action
38
+ # if actions are named (not required).
39
+ # :next=>action to take for this transition, one of:
40
+ # 1) state name(symbol), or special symbol (:stay), which stays at current state
41
+ # 2) hash{:state=>state name(symbol), :action=>code/lambda(string), :name=>id of action, for decider}
42
+ # 3) array of 1 and/or 2. Decider required in this case.
43
+ #
44
+ # Example 1:
45
+ # state_transition :state=>:c_state, :event=>:w_event, :decider => :c_state_decider,
46
+ # :next=>[
47
+ # {:state=>:a_state, :action=>lambda{|o, args| o.increment_value(7)} },
48
+ # {:state=>:b_state, :action=>lambda{|o, args| o.increment_value(-10)} }
49
+ # ]
50
+ #
51
+ # Example 2 (stays at current state):
52
+ # state_transition :state=>:c_state, :event=>:y_event, :next=>{:state=>:stay, :action=>lambda{|o, args| o.increment_value(-3)}}
53
+ #
54
+ # Example 3:
55
+ # state_transition :state=>:b_state, :event=>:z_event, :next=>:a_state
56
+ def state_transition(opts)
57
+ @machine.add_transition(opts)
58
+ end
59
+
60
+ def default_state; @machine.default_state; end
61
+ def states; @machine.states; end
62
+ def events; @machine.events; end
63
+ def transitions; @machine.transitions; end
64
+ def next_state(*args); @machine.next_state(*args); end
65
+ def next_state_instruction(*args); @machine.next_state_instruction(*args); end
66
+ end
67
+
68
+ module InstanceMethods
69
+ attr_accessor :event_history
70
+
71
+ def initialize(args=nil)
72
+ @current_state = default_state
73
+ @event_history = BoundedArray.new
74
+ # Attempt to call super:
75
+ begin
76
+ super(args)
77
+ rescue ArgumentError
78
+ super()
79
+ end
80
+ #puts "Current state is #{@current_state} #{@current_state.class}"
81
+ end
82
+
83
+ def send_event(event)
84
+ # puts "Sending event: #{event}"
85
+ check_current_state
86
+ next_state_instruction = self.class.next_state_instruction(@current_state, event)
87
+ if next_state_instruction.nil?
88
+ cs = @current_state
89
+ # This was causing problems in unit tests:
90
+ # @current_state = default_state
91
+ # puts "Returned to default state: [#{@current_state}]."
92
+ raise InvalidStateError, "No valid next state for #{cs.inspect} using event #{event.inspect} (#{cs.class}/#{event.class})."
93
+ end
94
+
95
+ if(next_state_instruction)
96
+ if(!String(next_state_instruction[:decider]).empty?)
97
+ # Decider present, call method:
98
+ decide_id = execute(next_state_instruction[:decider], event)
99
+
100
+ # Error checking:
101
+ if String(decide_id).empty?
102
+ raise InvalidStateError,
103
+ "Decider returned blank/nil for #{@current_state} using event #{event}. Next state from decider: [#{decide_id.inspect}]. Possible next states are [#{next_state_instruction[:next].inspect}]"
104
+ end
105
+
106
+ # Find next state:
107
+ instruction = Array(next_state_instruction[:next]).detect { |i|
108
+ i[:state].to_sym==decide_id.to_sym or i[:name].to_sym==decide_id.to_sym
109
+ }
110
+
111
+ # Error checking:
112
+ if instruction.nil?
113
+ raise InvalidStateError,
114
+ "No valid next instruction for #{@current_state} using event #{event}. Next state from decider: [#{decide_id.inspect}(#{decide_id.class})]. Possible next states are [#{next_state_instruction[:next].inspect}]"
115
+ end
116
+
117
+ # Do it:
118
+ process_instruction(instruction, event)
119
+ else
120
+ # Do it:
121
+ process_instruction(next_state_instruction[:next], event)
122
+ end
123
+ @current_state
124
+ end
125
+ @event_history.push(event)
126
+ end
127
+
128
+ def current_state
129
+ check_current_state
130
+ @current_state
131
+ end
132
+
133
+ def current_state=state
134
+ @current_state = state.to_sym
135
+ end
136
+
137
+ def default_state
138
+ (self.class.default_state) ? self.class.default_state : self.class.states.first
139
+ end
140
+
141
+ private
142
+
143
+ def process_instruction(instruction, event)
144
+ if instruction.nil?
145
+ @current_state = default_state
146
+ puts "Returned to default state: [#{@current_state}]."
147
+ raise InvalidStateError, "No valid next instruction for #{@current_state} using event #{event}"
148
+ end
149
+
150
+ if(instruction.is_a?Symbol)
151
+ change_state(instruction)
152
+ else
153
+ #puts "Processing action: #{instruction[:action]}"
154
+ execute(instruction[:action], event)
155
+ change_state(instruction[:state])
156
+ end
157
+ end
158
+
159
+ def execute(action, event)
160
+ unless action.nil?
161
+ if Symbol === action
162
+ self.method(action).call(:event=>event)
163
+ else
164
+ action.call(self, :event=>event)
165
+ end
166
+ end
167
+ end
168
+
169
+ def change_state(state_sym)
170
+ case(state_sym)
171
+ when :back
172
+ raise InvalidStateError, "Back is reserved but not yet implemented."
173
+ when :stay
174
+ @current_state = @current_state # nop, but should queue
175
+ else
176
+ @current_state = state_sym
177
+ end
178
+ end
179
+
180
+ def check_current_state
181
+ raise InvalidStateError, "No valid current state. Please call super() from state machine impl." if @current_state.nil?
182
+ end
183
+
184
+ end
185
+
186
+ class Machine
187
+ attr_accessor :states
188
+ attr_accessor :events
189
+ attr_accessor :transitions
190
+ attr_accessor :default_state
191
+
192
+ def initialize(opts)
193
+ @states=opts[:states].collect{|state| state.to_sym}
194
+ @events=opts[:events].collect{|event| event.to_sym}
195
+ @default_state=opts[:default_state].to_sym if opts[:default_state]
196
+ @transitions=[]
197
+ end
198
+
199
+ def add_transition(opts)
200
+ new_opts=strings_to_sym(opts)
201
+
202
+ # Some validation for arrays of actions:
203
+ if (new_opts[:next].is_a?Array)
204
+ if new_opts[:next].length==1
205
+ new_opts[:next] = new_opts[:next].first
206
+ elsif new_opts[:next].length>1
207
+ raise ArgumentError, "A decider must be present for multiple actions." if String(new_opts[:decider]).empty?
208
+ end
209
+ end
210
+
211
+ @transitions << new_opts
212
+ end
213
+
214
+ def strings_to_sym(hash)
215
+ new_hash=hash.dup
216
+ hash.each do |k, v|
217
+ unless (v.nil? or (v.respond_to?:empty? and v.empty? ))
218
+ if (v.is_a?(Hash))
219
+ new_hash[k] = strings_to_sym(v)
220
+ elsif (v.is_a?(Array))
221
+ new_array = v.dup
222
+ v.each_with_index do |array_hash, index|
223
+ new_array[index] = strings_to_sym(array_hash) if array_hash.is_a?(Hash)
224
+ end
225
+ new_hash[k]=new_array
226
+ elsif (v.is_a?(String))
227
+ new_hash[k] = v.to_sym
228
+ end
229
+ end
230
+ end
231
+ new_hash
232
+ end
233
+
234
+ # Next state (symbol only) for given state and event.
235
+ # (equivalent to next_state_instruction(state, event)[:next_state])
236
+ def next_state(state, event)
237
+ transition = next_state_instruction(state, event)
238
+ ns = transition ? transition[:next] : nil
239
+ (ns.is_a?Symbol) ? ns : ns && ns[:state]
240
+ end
241
+
242
+ # Next state instruction (as hash) for given state and event:
243
+ def next_state_instruction(state, event)
244
+ @transitions.detect{|t| t[:state]==state and t[:event]==event}
245
+ end
246
+ end
247
+
248
+ class InvalidStateError < Exception #:nodoc:
249
+
250
+ end
251
+
252
+ end
@@ -0,0 +1,8 @@
1
+ module RubyStateMachine
2
+ module VERSION
3
+ MAJOR = 1
4
+ MINOR = 2
5
+ TINY = 0
6
+ STRING = [MAJOR, MINOR, TINY].join('.')
7
+ end
8
+ end
metadata CHANGED
@@ -1,85 +1,50 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: ruby-state-machine
3
- version: !ruby/object:Gem::Version
4
- version: 0.0.3
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.2.0
5
5
  platform: ruby
6
- authors:
7
- - stevenmiers
8
- autorequire:
6
+ authors:
7
+ - tangledpath
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
-
12
- date: 2009-08-18 00:00:00 -05:00
13
- default_executable:
14
- dependencies:
15
- - !ruby/object:Gem::Dependency
16
- name: hoe
17
- type: :development
18
- version_requirement:
19
- version_requirements: !ruby/object:Gem::Requirement
20
- requirements:
21
- - - ">="
22
- - !ruby/object:Gem::Version
23
- version: 2.3.3
24
- version:
25
- description: FIX (describe your package)
26
- email:
11
+ date: 2020-11-04 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: A ruby state machine
14
+ email:
27
15
  - steven.miers@gmail.com
28
16
  executables: []
29
-
30
17
  extensions: []
31
-
32
- extra_rdoc_files:
33
- - History.txt
34
- - Manifest.txt
35
- - PostInstall.txt
36
- - website/index.txt
37
- files:
38
- - History.txt
39
- - Manifest.txt
40
- - PostInstall.txt
41
- - README.rdoc
42
- - Rakefile
43
- - config/website.yml
18
+ extra_rdoc_files:
19
+ - README.md
20
+ files:
21
+ - README.md
44
22
  - lib/ruby-state-machine.rb
45
- - script/console
46
- - script/destroy
47
- - script/generate
48
- - script/txt2html
49
- - test/test_helper.rb
50
- - test/test_state_machine.rb
51
- - website/index.html
52
- - website/index.txt
53
- - website/javascripts/rounded_corners_lite.inc.js
54
- - website/stylesheets/screen.css
55
- - website/template.html.erb
56
- has_rdoc: true
57
- homepage: http://github.com/#{github_username}/#{project_name}
58
- post_install_message: PostInstall.txt
59
- rdoc_options:
60
- - --main
61
- - README.rdoc
62
- require_paths:
23
+ - lib/ruby-state-machine/bounded_array.rb
24
+ - lib/ruby-state-machine/state_machine.rb
25
+ - lib/ruby-state-machine/version.rb
26
+ homepage: http://github.com/tangledpath/ruby-state-machine
27
+ licenses: []
28
+ metadata: {}
29
+ post_install_message:
30
+ rdoc_options: []
31
+ require_paths:
63
32
  - lib
64
- required_ruby_version: !ruby/object:Gem::Requirement
65
- requirements:
33
+ - ext
34
+ required_ruby_version: !ruby/object:Gem::Requirement
35
+ requirements:
66
36
  - - ">="
67
- - !ruby/object:Gem::Version
68
- version: "0"
69
- version:
70
- required_rubygems_version: !ruby/object:Gem::Requirement
71
- requirements:
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ required_rubygems_version: !ruby/object:Gem::Requirement
40
+ requirements:
72
41
  - - ">="
73
- - !ruby/object:Gem::Version
74
- version: "0"
75
- version:
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
76
44
  requirements: []
77
-
78
- rubyforge_project: ruby-state-mach
79
- rubygems_version: 1.3.1
80
- signing_key:
81
- specification_version: 2
82
- summary: FIX (describe your package)
83
- test_files:
84
- - test/test_helper.rb
85
- - test/test_state_machine.rb
45
+ rubyforge_project: ruby-state-machine
46
+ rubygems_version: 2.6.10
47
+ signing_key:
48
+ specification_version: 4
49
+ summary: A full-featured state machine gem for use within ruby.
50
+ test_files: []