simpler_state_machine 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.
data/.DS_Store ADDED
Binary file
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in simple_state_machine.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Elad Meidar
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,98 @@
1
+ # Note:
2
+
3
+ This code used to contain some offensive wording as part of our test suite that was not removed or at least moderated while
4
+ moving this project from private to public status. the people that worked on this project and myself especially are sincerely sorry and we absolutely 100% didn't mean
5
+ to offend anyone. sorry. :(
6
+
7
+ # SimplerStateMachine
8
+
9
+ __SimplerStateMachine__ is a simple (suprisingly) state machine aimed to provide a simpler implementation and setup for ruby based state machines.
10
+ It supports:
11
+
12
+ * States as enum
13
+ * Events / Callbacks
14
+ * Transitions
15
+
16
+ ## Installation
17
+
18
+ Add this line to your application's Gemfile:
19
+
20
+ gem 'simpler_state_machine'
21
+
22
+ And then execute:
23
+
24
+ $ bundle
25
+
26
+ Or install it yourself as:
27
+
28
+ $ gem install simple_state_machine
29
+
30
+ ## Usage
31
+
32
+ __SimplerStateMachine__ provides an easy interface to add states and transitions:
33
+
34
+
35
+ ### Declaration
36
+
37
+ Class Elad
38
+
39
+ include SimpleStateMachine
40
+
41
+ state_machine do |sm|
42
+ sm.initial_state "happy"
43
+ sm.state_field "status"
44
+ sm.add_state "mad", :before_enter => :kill_miki,
45
+ :after_enter => :kiss_miki,
46
+ :before_leave => :kick_miki,
47
+ :after_leave => :keep_miki,
48
+ :on_error => :tell_mom
49
+ sm.add_state 'calm', :after_enter => :kiss_miki
50
+ sm.add_transition :get_angry, :from => "happy", :to => "mad"
51
+ sm.add_transition :calmate, :from => ["happy", "mad"], :to => "calm"
52
+ end
53
+ end
54
+
55
+ ### Configuration Options
56
+
57
+ * `initial_state`: Sets up the initial state the class will get when instantiating a new instance.
58
+ * `state_field`: the field / attribute that keeps the status enum (defaults to "status")
59
+ * `add_state`: adding a state, accepts a state name and a list of callbacks.
60
+ * `add_transition`: defines a transtions with destination state and source state (or multiple in an array)
61
+ * `persistency_method`: __SimpleStateMachine__ doesn't rely on the existance of a persistency layer (ActiveRecord for example), so if you want to have this option you'll need to specify the persistency method to use (in ActiveRecord's case, use "save" or "save!")
62
+
63
+ ### Accessing States
64
+
65
+ __SimplerStateMachine__ will save the state's enum value as integer to the defined attribute, in order to access the string / constant representation of the state use `#enum_state`:
66
+
67
+ > elad = Elad.new
68
+ > elad.status
69
+ 0
70
+ > elad.human_status_name
71
+ "happy"
72
+
73
+ ### Callbacks / Events
74
+
75
+ __SimplerStateMachine__ makes several callbacks available when defining a state and will fire them when a state transtion is made.
76
+ The available callbacks are:
77
+
78
+ * `:before_enter`: runs before the state is entered.
79
+ * `:after_enter`: runs after the state transition is completed
80
+ * `:before_leave`: runs before a transition to another state starts
81
+ * `:after_leave`: runs after a transition to another state runs
82
+ * `:on_error`: runs when the transition fails
83
+
84
+ The order in which the callbacks are called is:
85
+
86
+ before_leave (of the current state)
87
+ before_enter (of the destination state)
88
+ <transition>
89
+ after_leave (of the current state)
90
+ after_enter (of the destination state)
91
+
92
+ ## Contributing
93
+
94
+ 1. Fork it
95
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
96
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
97
+ 4. Push to the branch (`git push origin my-new-feature`)
98
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+
4
+ require 'rspec/core'
5
+ require 'rspec/core/rake_task'
6
+ RSpec::Core::RakeTask.new(:spec) do |spec|
7
+ spec.pattern = FileList['spec/**/*_spec.rb']
8
+ spec.rspec_opts = "--format documentation --color"
9
+ end
10
+
11
+ require 'rake/testtask'
12
+ Rake::TestTask.new(:test) do |test|
13
+ test.libs << 'lib' << 'test'
14
+ test.pattern = 'test/**/*_test.rb'
15
+ test.verbose = true
16
+ end
17
+
18
+ require 'rdoc/task'
19
+ require 'simple_state_machine/version'
20
+
21
+ Rake::RDocTask.new do |rdoc|
22
+ rdoc.rdoc_dir = 'rdoc'
23
+ rdoc.title = "Simple State Machine #{SimpleStateMachine::VERSION}"
24
+ rdoc.rdoc_files.include('README*')
25
+ rdoc.rdoc_files.include('lib/**/*.rb')
26
+ end
27
+
28
+ task :default => :spec
@@ -0,0 +1,169 @@
1
+ module SimpleStateMachine
2
+ class Base
3
+
4
+ attr_reader :states, # Holds the state enum
5
+ :events, # Hash of hashes, holds the callbacks list from each state when the state name is the key
6
+ :transitions, # Hash of hashes, holds the list of available transitions for each state.
7
+ :initial_state, # Define the initial state.
8
+ :state_field, # The attribute that will store the status integer representation
9
+ :persistency_method, # a method to ensure some persistency on a record. refer to the readme.
10
+ :states_constant_name # the name of the constant to be defined with the states enum
11
+
12
+ attr_accessor :clazz
13
+
14
+ def initialize(clazz)
15
+ @clazz = clazz
16
+ @_states = []
17
+ @events = {}
18
+ @transitions = {}
19
+ @state_field = "status"
20
+ @states_constant_name = "STATES"
21
+ @persistency_method = nil
22
+ end
23
+
24
+ # A #clone callback. we ensure that even the sub elements are cloned - 'deep copy'
25
+ def initialize_copy(orig)
26
+ super
27
+ @states = orig.states.clone
28
+ @_states = orig.instance_variable_get("@_states").clone
29
+ @events = orig.events.clone
30
+ @transitions = orig.transitions.clone
31
+ @initial_state = orig.initial_state.clone
32
+ @state_field = orig.state_field
33
+ @persistency_method = orig.persistency_method.clone unless @persistency_method.nil?
34
+ @states_constant_name = orig.states_constant_name.clone
35
+ end
36
+
37
+ def states_constant_name(constant_name = nil)
38
+ if constant_name.nil?
39
+ @states_constant_name
40
+ else
41
+ @states_constant_name = constant_name.upcase
42
+ end
43
+ end
44
+
45
+ def persistency_method(method_name = nil)
46
+ if method_name.nil?
47
+ @persistency_method
48
+ else
49
+ @persistency_method = method_name
50
+ end
51
+ end
52
+
53
+ def state_field(state_field = nil)
54
+ if state_field.nil?
55
+ @state_field
56
+ else
57
+ @state_field = state_field
58
+ end
59
+ end
60
+
61
+ def initial_state(initial_state = nil)
62
+ if initial_state.nil?
63
+ @initial_state
64
+ else
65
+ add_state(initial_state)
66
+ @initial_state = initial_state
67
+ end
68
+ end
69
+
70
+ # Add a new state
71
+ def add_state(state_name, options = {})
72
+
73
+ # Fill in missing with callbacks with empty procs.
74
+ options = { :before_enter => Proc.new { true },
75
+ :after_enter => Proc.new { true },
76
+ :before_leave => Proc.new { true },
77
+ :after_leave => Proc.new { true },
78
+ :on_error => Proc.new { |error| true } }.merge(options)
79
+
80
+ # Append to the states arry
81
+ @_states << human_state_name(state_name)
82
+
83
+ # Register events
84
+ @events[human_state_name(state_name)] = options.dup
85
+
86
+ @clazz.instance_eval do
87
+
88
+ # define a "#state?" method on the object instance
89
+ define_method("#{state_name}?") do
90
+ self.send(self.class.state_machine.state_field) == self.class.state_machine.states[state_name].to_i
91
+ end
92
+ end
93
+ end
94
+
95
+ # Add transition
96
+ def add_transition(transition_name, options = {})
97
+
98
+ # Convert from states to array
99
+ options[:from] = options[:from].is_a?(Array) ? options[:from] : options[:from].to_a
100
+
101
+ # Register transactions
102
+ @transitions[transition_name.to_s] = options
103
+ @clazz.instance_eval do
104
+
105
+ # Define the transition method
106
+ define_method("#{transition_name}") do |*args|
107
+
108
+ persist = args.first if args.any?
109
+ persist |= false
110
+
111
+ # check if the transition is available to the current state
112
+ if self.class.state_machine.transitions[transition_name.to_s] && self.class.state_machine.transitions[transition_name.to_s][:from].include?(self.enum_status.to_sym.to_s)
113
+
114
+ # Get the current state events
115
+ current_state_events = self.class.state_machine.events[self.enum_status.to_sym.to_s]
116
+ next_state_events = self.class.state_machine.events[self.class.state_machine.transitions[transition_name.to_s][:to]]
117
+
118
+ begin
119
+ # Fire events and perform transition (and persistence if needed)
120
+ fire_state_machine_event(current_state_events[:before_leave])
121
+ fire_state_machine_event(next_state_events[:before_enter])
122
+ self.send("#{self.class.state_machine.state_field}=", self.class.state_machine.states[self.class.state_machine.transitions[transition_name.to_s][:to]].to_i)
123
+ self.send(self.class.state_machine.persistency_method) if self.class.state_machine.persistent_mode? && persist == true
124
+ fire_state_machine_event(current_state_events[:after_leave])
125
+ fire_state_machine_event(next_state_events[:after_enter])
126
+ rescue Exception => e
127
+ fire_state_machine_event(current_state_events[:on_error])
128
+ end
129
+ return true
130
+ else
131
+ return false
132
+ end
133
+ end
134
+
135
+ # Define a transition method with a shebang
136
+ define_method("#{transition_name}!") do
137
+ if !(self.send(transition_name, true))
138
+ raise Exceptions::InvalidTransition, "Could not transit '#{self.enum_status.to_sym.to_s}' to '#{options[:to]}'"
139
+ else
140
+ return true
141
+ end
142
+ end
143
+ end
144
+ end
145
+
146
+
147
+ # Transform the states array to enum
148
+ def enumize!
149
+ @states = Enum.new(*@_states)
150
+ end
151
+
152
+ # Persistency method
153
+ def persistent_mode?
154
+ !(@persistency_method.nil?)
155
+ end
156
+
157
+ def states
158
+ @states
159
+ end
160
+
161
+ protected
162
+
163
+ private
164
+
165
+ def human_state_name(state)
166
+ state.to_s.downcase
167
+ end
168
+ end
169
+ end
@@ -0,0 +1,29 @@
1
+ class Enum < Hash
2
+
3
+ def initialize(*members)
4
+ super()
5
+ @rev = {}
6
+ members.each_with_index {|m,i| self[i] = m }
7
+ end
8
+
9
+ def [](k)
10
+ super || @rev[k]
11
+ end
12
+
13
+ def []=(k,v)
14
+ @rev[v] = k
15
+ super
16
+ end
17
+ end
18
+
19
+ class StrictEnum < Hash
20
+
21
+ module Exceptions
22
+ class MemberNotFound < ArgumentError; end
23
+ end
24
+
25
+ def [](k)
26
+ super || raise(Exceptions::MemberNotFound, "index/memeber '#{k}' not found in enum")
27
+ end
28
+
29
+ end
@@ -0,0 +1,6 @@
1
+ module SimpleStateMachine
2
+
3
+ module Exceptions
4
+ class InvalidTransition < RuntimeError; end
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ # FFU
2
+ module SimpleStateMachine
3
+ class Event
4
+ end
5
+ end
@@ -0,0 +1,86 @@
1
+ =begin
2
+ SimpleStateMachine
3
+ ==================
4
+
5
+ This module should be mixed in to a class that needs a state machine implementation. refer to the readme.
6
+ =end
7
+ module SimpleStateMachine
8
+
9
+ def self.included(base) #:nodoc:
10
+ base.class_eval do
11
+ extend ClassMethods
12
+ include InstanceMethods
13
+
14
+ # Alias the enum reference method for the confused end user.
15
+ alias :enum_state :enum_status
16
+
17
+ end
18
+ end
19
+
20
+ module ClassMethods
21
+
22
+ # When inherited - duplicate the state machine from the parent class and allow the extend it
23
+ def inherited(child)
24
+ me = self
25
+ child.class_eval do
26
+ @sm = me.state_machine.clone
27
+ @sm.clazz = self
28
+ end
29
+ end
30
+
31
+ # Create or return a state machine
32
+ def state_machine(&block)
33
+ return @sm if !(@sm.nil?) && !(block_given?)
34
+ @sm ||= SimpleStateMachine::Base.new(self)
35
+ @sm.instance_eval(&block) if block_given?
36
+ @sm.enumize!
37
+ self.const_set(@sm.states_constant_name,@sm.states).freeze
38
+ return @sm
39
+ end
40
+ end
41
+
42
+ module InstanceMethods
43
+
44
+ # Set the initial status value
45
+ def initialize(*args)
46
+ self.send("#{self.class.state_machine.state_field}=", self.class.state_machine.states[self.class.state_machine.initial_state].to_i)
47
+
48
+ begin
49
+ super
50
+ rescue ArgumentError # Catch in case the super does not get parameters
51
+ super()
52
+ end
53
+ end
54
+
55
+ # Return the enum status
56
+ def enum_status
57
+ self.class.state_machine.states[self.current_state]
58
+ end
59
+
60
+ # Return the current state
61
+ def current_state
62
+ self.send(self.class.state_machine.state_field)
63
+ end
64
+
65
+ # human name for status
66
+ def human_status_name
67
+ enum_status
68
+ end
69
+
70
+ private
71
+
72
+ # Fire a callback, support a symbol, string or an array of symbols / strings
73
+ def fire_state_machine_event(event)
74
+ case event.class.name
75
+ when "Proc"
76
+ event.call
77
+ when "Symbol", "String"
78
+ self.send(event)
79
+ when "Array"
80
+ event.each do |e|
81
+ fire_state_machine_event(e)
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,13 @@
1
+ # FFU
2
+ module SimpleStateMachine #:nodoc
3
+ class Transition
4
+
5
+ attr_accessor :to, :from
6
+
7
+ def initialize(options = {})
8
+ options.assert_valid_keys :from, :to
9
+ @to = options[:to]
10
+ @from = options[:from]
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,3 @@
1
+ module SimpleStateMachine
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,9 @@
1
+ # Require all the files
2
+ require File.join(File.dirname(__FILE__), 'simple_state_machine', 'version')
3
+ require File.join(File.dirname(__FILE__), 'simple_state_machine', 'simple_state_machine')
4
+ require File.join(File.dirname(__FILE__), 'simple_state_machine', 'base')
5
+ require File.join(File.dirname(__FILE__), 'simple_state_machine', 'event')
6
+ require File.join(File.dirname(__FILE__), 'simple_state_machine', 'enum')
7
+ require File.join(File.dirname(__FILE__), 'simple_state_machine', 'errors')
8
+ require File.join(File.dirname(__FILE__), 'simple_state_machine', 'transition')
9
+
@@ -0,0 +1,28 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/simple_state_machine/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Elad Meidar"]
6
+ gem.email = ["elad@eizesus.com"]
7
+ gem.description = "A simple state machine that supports enum"
8
+ gem.summary = "A simple lightweight state machine that uses an enum type to store states"
9
+ gem.homepage = "http://devandpencil.herokuapp.com/blog/2013/01/30/simplestatemachine-a-simple-enum-based-state-machine-for-ruby/"
10
+
11
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
12
+ gem.files = `git ls-files`.split("\n")
13
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
14
+ gem.name = "simpler_state_machine"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = SimpleStateMachine::VERSION
17
+
18
+ gem.add_development_dependency 'activerecord'
19
+ gem.add_development_dependency 'rake'
20
+ gem.add_development_dependency 'sdoc'
21
+ gem.add_development_dependency 'rspec', '~> 2.0'
22
+ gem.add_development_dependency 'rr'
23
+ gem.add_development_dependency 'shoulda'
24
+ gem.add_development_dependency 'sqlite3'
25
+ gem.add_development_dependency 'minitest'
26
+ gem.add_development_dependency 'ruby-debug'
27
+ gem.add_development_dependency 'ruby-debug-completion'
28
+ end
data/spec/database.yml ADDED
@@ -0,0 +1,3 @@
1
+ sqlite3:
2
+ adapter: sqlite3
3
+ database: spec/simple_state_machine.sqlite3.db
@@ -0,0 +1,21 @@
1
+ class ExpressGate < Gate
2
+
3
+ attr_accessor :new_express_record
4
+
5
+ state_machine do |sm|
6
+ sm.initial_state "express_beginner"
7
+ sm.add_state "express_expert", :after_enter => :add_prize
8
+ sm.add_state "miki_level", :after_enter => :add_prize
9
+
10
+ #sm.add_state "expert", :after_enter => :add_express_prize
11
+
12
+ sm.add_transition :make_express_beginner, :from => "expert", :to => "express_beginner"
13
+ sm.add_transition :demote_express_beginner, :from => "express_beginner", :to => "expert"
14
+ sm.add_transition :make_express_expert, :from => "express_beginner", :to => "express_expert"
15
+ sm.add_transition :make_miki, :from => "express_expert", :to => "miki_level"
16
+ end
17
+
18
+ def initialize(initial_status = nil)
19
+ super
20
+ end
21
+ end
@@ -0,0 +1,33 @@
1
+ class Gate
2
+ include SimpleStateMachine
3
+
4
+ attr_accessor :prize_won, :status, :new_record
5
+
6
+ state_machine do |sm|
7
+ sm.initial_state "beginner"
8
+ sm.add_state "novice", :after_enter => :add_prize
9
+ sm.add_state "expert", :after_enter => :add_prize
10
+ sm.add_transition :make_novice, :from => ["beginner", "expert"], :to => "novice"
11
+ sm.add_transition :make_expert, :from => "novice", :to => "expert"
12
+ end
13
+
14
+ def add_prize
15
+ self.prize_won ||= ""
16
+ self.prize_won += "*"
17
+ end
18
+
19
+ def dummy_save
20
+ @new_record = false
21
+ end
22
+
23
+ def new_record?
24
+ @new_record
25
+ end
26
+
27
+ def initialize(initial_status = nil)
28
+ @status = initial_status
29
+ @new_record = true
30
+ @prize_won = ""
31
+ super
32
+ end
33
+ end
@@ -0,0 +1,14 @@
1
+ class KingGate < Gate
2
+
3
+ state_machine do |sm|
4
+ sm.initial_state "king_beginner"
5
+ sm.add_state "king_novice", :after_enter => :add_prize
6
+ sm.add_state "king_expert", :after_enter => :add_prize
7
+ sm.add_transition :make_king_novice, :from => "king_beginner", :to => "king_novice"
8
+ sm.add_transition :make_king_expert, :from => "king_novice", :to => "king_expert"
9
+ end
10
+
11
+ def initialize(initial_status = nil)
12
+ super
13
+ end
14
+ end
data/spec/schema.rb ADDED
@@ -0,0 +1,9 @@
1
+ ActiveRecord::Schema.define(:version => 0) do
2
+
3
+ %w{gates readers writers transients simples simple_new_dsls thieves localizer_test_models}.each do |table_name|
4
+ create_table table_name, :force => true do |t|
5
+ t.string "status"
6
+ t.string "better_status"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,16 @@
1
+ $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__)))
2
+ $LOAD_PATH.unshift(File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')))
3
+ require 'simple_state_machine'
4
+
5
+ require 'rspec'
6
+ require 'rspec/autorun'
7
+
8
+ # require 'ruby-debug'; Debugger.settings[:autoeval] = true; debugger; rubys_debugger = 'annoying'
9
+ # require 'ruby-debug/completion'
10
+
11
+ # Requiring custom spec helpers
12
+ Dir[File.dirname(__FILE__) + "/spec_helpers/**/*.rb"].sort.each { |f| require File.expand_path(f) }
13
+ #Dir[File.dirname(__FILE__) + "/models/*.rb"].each { |f| require File.expand_path(f) }
14
+ require "models/gate"
15
+ require 'models/express_gate'
16
+ require 'models/king_gate'
@@ -0,0 +1,297 @@
1
+ require 'spec_helper'
2
+ require 'ruby-debug'
3
+
4
+ describe SimpleStateMachine do
5
+ describe "- Normal state machine" do
6
+ before(:each) do
7
+ @gate = Gate.new
8
+ @gate.should respond_to(:status)
9
+ end
10
+
11
+ describe "- Basic operations" do
12
+
13
+ it "Should allow accessible methods to check if certain state is active on the including class" do
14
+ @gate.methods.should include("beginner?")
15
+ @gate.methods.should include("novice?")
16
+ @gate.methods.should include("expert?")
17
+ end
18
+
19
+ it "should have a state_machine reference" do
20
+ @gate.class.should respond_to(:state_machine)
21
+ end
22
+
23
+ it "should respond to the default state field" do
24
+ @gate.should respond_to(Gate.state_machine.state_field)
25
+ end
26
+
27
+ it "should set the default state field to 'status'" do
28
+ Gate.state_machine.state_field.should eql("status")
29
+ end
30
+
31
+ it "should set an initial status" do
32
+ @gate.status.should eql(Gate.state_machine.states["beginner"].to_i)
33
+ end
34
+
35
+ it "should force an initial status even if one specified by a default class constructor" do
36
+ @gate = Gate.new("bogus_status")
37
+ @gate.beginner?.should be_true
38
+ end
39
+ end
40
+
41
+ describe "- Transitions" do
42
+
43
+ it "should allow a gate to transition from 'beginner' to 'novice', normal method" do
44
+ lambda {
45
+ @gate.make_novice.should be_true
46
+ }.should_not raise_error(SimpleStateMachine::Exceptions::InvalidTransition)
47
+
48
+ @gate.novice?.should be_true
49
+ end
50
+
51
+ it "should allow a gate to transition from 'beginner' to 'novice', with shebang" do
52
+ lambda {
53
+ @gate.make_novice!
54
+ }.should_not raise_error(SimpleStateMachine::Exceptions::InvalidTransition)
55
+
56
+ @gate.novice?.should be_true
57
+ end
58
+
59
+ it "should not allow a gate to transition from 'beginner' to 'expert'" do
60
+ lambda {
61
+ @gate.make_expert.should be_false
62
+ }.should_not raise_error(SimpleStateMachine::Exceptions::InvalidTransition)
63
+
64
+ @gate.beginner?.should be_true
65
+ end
66
+
67
+ it "should raise an exception if a gate tries to transition from 'beginner' to 'expert' with a shebang" do
68
+ lambda {
69
+ @gate.make_expert!
70
+ }.should raise_error(SimpleStateMachine::Exceptions::InvalidTransition, "Could not transit 'beginner' to 'expert'")
71
+
72
+ @gate.beginner?.should be_true
73
+ end
74
+
75
+ it "should allow a gate to transtition from 'expert' back to 'novice', normal method" do
76
+ @gate.status = Gate.state_machine.states["expert"]
77
+ lambda {
78
+ @gate.make_novice.should be_true
79
+ }.should_not raise_error(SimpleStateMachine::Exceptions::InvalidTransition)
80
+
81
+ @gate.novice?.should be_true
82
+ end
83
+
84
+ it "should allow a gate to transtition from 'expert' back to 'novice', with shebang" do
85
+ @gate.status = Gate.state_machine.states["expert"]
86
+ lambda {
87
+ @gate.make_novice!
88
+ }.should_not raise_error(SimpleStateMachine::Exceptions::InvalidTransition)
89
+
90
+ @gate.novice?.should be_true
91
+ end
92
+
93
+ end
94
+
95
+ describe "- Persistency" do
96
+ before(:all) do
97
+ Gate.state_machine.persistency_method(:dummy_save)
98
+ end
99
+
100
+ it "should persist on a shebang persistency transition method" do
101
+ @gate.new_record?.should be_true
102
+ @gate.make_novice!
103
+ @gate.new_record?.should be_false
104
+ @gate.novice?.should be_true
105
+ end
106
+
107
+ it "should not persist on a non-shebang persistency transition method" do
108
+ @gate.new_record?.should be_true
109
+ @gate.make_novice.should be_true
110
+ @gate.new_record?.should be_true
111
+ @gate.novice?.should be_true
112
+ end
113
+ end
114
+
115
+ describe "- Events" do
116
+ it "should add prizes to the gate when transitioning from 'beginner' to 'novice'" do
117
+ @gate.prize_won.should be_empty
118
+ @gate.make_novice.should be_true
119
+ @gate.prize_won.should eql("*")
120
+ @gate.novice?.should be_true
121
+ end
122
+ end
123
+ end
124
+
125
+ describe "- Inheritence" do
126
+ describe "- parent class state machine" do
127
+ before(:each) do
128
+ #create both KingGate & ExpressGate to fail class level attributes integrity
129
+ @kinggate = KingGate.new
130
+ @gate = ExpressGate.new
131
+ @gate.should respond_to(:status)
132
+ end
133
+
134
+ describe "- Basic operations" do
135
+ it "Should allow accessible constants on the including class" do
136
+ ExpressGate.constants.should include("STATES")
137
+ ExpressGate::STATES["beginner"].should_not be_nil
138
+ ExpressGate::STATES["novice"].should_not be_nil
139
+ ExpressGate::STATES["expert"].should_not be_nil
140
+ ExpressGate::STATES["express_beginner"].should_not be_nil
141
+ ExpressGate::STATES["express_expert"].should_not be_nil
142
+ ExpressGate::STATES["miki_level"].should_not be_nil
143
+ end
144
+
145
+ it "Should allow accessible methods to check if certain state is active on the including class for parent class' states" do
146
+ @gate.methods.should include("beginner?")
147
+ @gate.methods.should include("novice?")
148
+ @gate.methods.should include("expert?")
149
+ end
150
+
151
+ it "Should allow accessible methods to check if certain state is active on the including class for child class' states" do
152
+ @gate.methods.should include("express_beginner?")
153
+ @gate.methods.should include("express_expert?")
154
+ @gate.methods.should include("miki_level?")
155
+ end
156
+
157
+ it "should have a state_machine reference" do
158
+ @gate.class.should respond_to(:state_machine)
159
+ end
160
+
161
+ it "should respond to the default state field" do
162
+ @gate.should respond_to(ExpressGate.state_machine.state_field)
163
+ end
164
+
165
+ it "should set the default state field to 'status'" do
166
+ ExpressGate.state_machine.state_field.should eql("status")
167
+ end
168
+
169
+ it "should set an initial status" do
170
+ @gate.status.should eql(ExpressGate.state_machine.states["express_beginner"].to_i)
171
+ end
172
+
173
+ it "should force an initial status even if one specified by a default class constructor" do
174
+ @gate = ExpressGate.new("bogus_status")
175
+ @gate.express_beginner?.should be_true
176
+ end
177
+ end
178
+
179
+ describe "- Transitions" do
180
+
181
+ it "should allow a gate to transition from a state that comes from parent 'expert' to 'express_beginner', normal method" do
182
+ lambda {
183
+ @gate.status = ExpressGate.state_machine.states["expert"]
184
+ @gate.make_express_beginner.should be_true
185
+ }.should_not raise_error(SimpleStateMachine::Exceptions::InvalidTransition)
186
+
187
+ @gate.express_beginner?.should be_true
188
+ end
189
+
190
+ it "should allow a gate to transition from a state that comes from parent 'expert' to 'express_beginner', shebang method" do
191
+ lambda {
192
+ @gate.status = ExpressGate.state_machine.states["expert"]
193
+ @gate.make_express_beginner!
194
+ }.should_not raise_error(SimpleStateMachine::Exceptions::InvalidTransition)
195
+
196
+ @gate.express_beginner?.should be_true
197
+ end
198
+
199
+ it "should not allow a gate to transition from a state that comes from parent 'beginner' to 'express_beginner', normal method" do
200
+ @gate.status = ExpressGate.state_machine.states["beginner"]
201
+ lambda {
202
+ @gate.make_express_beginner.should be_false
203
+ }.should_not raise_error(SimpleStateMachine::Exceptions::InvalidTransition)
204
+
205
+ @gate.beginner?.should be_true
206
+ end
207
+
208
+ it "should not allow a gate to transition from a state that comes from parent 'beginner' to 'express_beginner', shebang method" do
209
+ @gate.status = ExpressGate.state_machine.states["beginner"]
210
+ lambda {
211
+ @gate.make_express_beginner!
212
+ }.should raise_error(SimpleStateMachine::Exceptions::InvalidTransition, "Could not transit 'beginner' to 'express_beginner'")
213
+
214
+ @gate.beginner?.should be_true
215
+ end
216
+
217
+ it "should allow a gate to transition from 'express_beginner' to 'express_expert', normal method" do
218
+ lambda {
219
+ @gate.make_express_expert.should be_true
220
+ }.should_not raise_error(SimpleStateMachine::Exceptions::InvalidTransition)
221
+
222
+ @gate.express_expert?.should be_true
223
+ end
224
+
225
+ it "should allow a gate to transition from 'express_beginner' to 'express_expert', with shebang" do
226
+ lambda {
227
+ @gate.make_express_expert!
228
+ }.should_not raise_error(SimpleStateMachine::Exceptions::InvalidTransition)
229
+
230
+ @gate.express_expert?.should be_true
231
+ end
232
+
233
+ it "should not allow a gate to transition from 'express_beginner' to 'miki_level'" do
234
+ lambda {
235
+ @gate.make_miki.should be_false
236
+ }.should_not raise_error(SimpleStateMachine::Exceptions::InvalidTransition)
237
+
238
+ @gate.express_beginner?.should be_true
239
+ end
240
+
241
+ it "should raise an exception if a gate tries to transition from 'express_beginner' to 'miki_level' with a shebang" do
242
+ lambda {
243
+ @gate.make_miki!
244
+ }.should raise_error(SimpleStateMachine::Exceptions::InvalidTransition, "Could not transit 'express_beginner' to 'miki_level'")
245
+
246
+ @gate.express_beginner?.should be_true
247
+ end
248
+
249
+ it "should allow a gate to transition from a state that comes from child 'express_beginner' to 'expert', normal method" do
250
+ @gate.status = ExpressGate.state_machine.states["express_beginner"]
251
+ lambda {
252
+ @gate.demote_express_beginner.should be_true
253
+ }.should_not raise_error(SimpleStateMachine::Exceptions::InvalidTransition)
254
+ @gate.expert?.should be_true
255
+ end
256
+
257
+ it "should allow a gate to transition from a state that comes from child 'express_beginner' to state from parent 'expert', shebang method" do
258
+ @gate.status = ExpressGate.state_machine.states["express_beginner"]
259
+ lambda {
260
+ @gate.demote_express_beginner!
261
+ }.should_not raise_error(SimpleStateMachine::Exceptions::InvalidTransition)
262
+
263
+ @gate.expert?.should be_true
264
+ end
265
+ end
266
+
267
+ describe "- Persistency" do
268
+ before(:all) do
269
+ ExpressGate.state_machine.persistency_method(:dummy_save)
270
+ end
271
+
272
+ it "should persist on a shebang transition method" do
273
+ @gate.new_record?.should be_true
274
+ @gate.make_express_expert!
275
+ @gate.new_record?.should be_false
276
+ @gate.express_expert?.should be_true
277
+ end
278
+
279
+ it "should not persist on a non-shebang transition method" do
280
+ @gate.new_record?.should be_true
281
+ @gate.make_express_expert
282
+ @gate.new_record?.should be_true
283
+ @gate.express_expert?.should be_true
284
+ end
285
+ end
286
+
287
+ describe "- Events" do
288
+ it "should add prizes to the gate when transitioning from 'beginner' to 'novice'" do
289
+ @gate.prize_won.should be_empty
290
+ @gate.make_express_expert
291
+ @gate.prize_won.should eql("*")
292
+ @gate.express_expert?.should be_true
293
+ end
294
+ end
295
+ end
296
+ end
297
+ end
metadata ADDED
@@ -0,0 +1,227 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: simpler_state_machine
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Elad Meidar
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-02-05 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activerecord
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: sdoc
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: rspec
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: '2.0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: '2.0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: rr
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: shoulda
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: sqlite3
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ - !ruby/object:Gem::Dependency
127
+ name: minitest
128
+ requirement: !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ! '>='
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ type: :development
135
+ prerelease: false
136
+ version_requirements: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ! '>='
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
142
+ - !ruby/object:Gem::Dependency
143
+ name: ruby-debug
144
+ requirement: !ruby/object:Gem::Requirement
145
+ none: false
146
+ requirements:
147
+ - - ! '>='
148
+ - !ruby/object:Gem::Version
149
+ version: '0'
150
+ type: :development
151
+ prerelease: false
152
+ version_requirements: !ruby/object:Gem::Requirement
153
+ none: false
154
+ requirements:
155
+ - - ! '>='
156
+ - !ruby/object:Gem::Version
157
+ version: '0'
158
+ - !ruby/object:Gem::Dependency
159
+ name: ruby-debug-completion
160
+ requirement: !ruby/object:Gem::Requirement
161
+ none: false
162
+ requirements:
163
+ - - ! '>='
164
+ - !ruby/object:Gem::Version
165
+ version: '0'
166
+ type: :development
167
+ prerelease: false
168
+ version_requirements: !ruby/object:Gem::Requirement
169
+ none: false
170
+ requirements:
171
+ - - ! '>='
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ description: A simple state machine that supports enum
175
+ email:
176
+ - elad@eizesus.com
177
+ executables: []
178
+ extensions: []
179
+ extra_rdoc_files: []
180
+ files:
181
+ - .DS_Store
182
+ - .gitignore
183
+ - Gemfile
184
+ - LICENSE
185
+ - README.md
186
+ - Rakefile
187
+ - lib/simple_state_machine.rb
188
+ - lib/simple_state_machine/base.rb
189
+ - lib/simple_state_machine/enum.rb
190
+ - lib/simple_state_machine/errors.rb
191
+ - lib/simple_state_machine/event.rb
192
+ - lib/simple_state_machine/simple_state_machine.rb
193
+ - lib/simple_state_machine/transition.rb
194
+ - lib/simple_state_machine/version.rb
195
+ - simpler_state_machine.gemspec
196
+ - spec/database.yml
197
+ - spec/models/express_gate.rb
198
+ - spec/models/gate.rb
199
+ - spec/models/king_gate.rb
200
+ - spec/schema.rb
201
+ - spec/spec_helper.rb
202
+ - spec/unit/simple_state_machine_spec.rb
203
+ homepage: http://devandpencil.herokuapp.com/blog/2013/01/30/simplestatemachine-a-simple-enum-based-state-machine-for-ruby/
204
+ licenses: []
205
+ post_install_message:
206
+ rdoc_options: []
207
+ require_paths:
208
+ - lib
209
+ required_ruby_version: !ruby/object:Gem::Requirement
210
+ none: false
211
+ requirements:
212
+ - - ! '>='
213
+ - !ruby/object:Gem::Version
214
+ version: '0'
215
+ required_rubygems_version: !ruby/object:Gem::Requirement
216
+ none: false
217
+ requirements:
218
+ - - ! '>='
219
+ - !ruby/object:Gem::Version
220
+ version: '0'
221
+ requirements: []
222
+ rubyforge_project:
223
+ rubygems_version: 1.8.25
224
+ signing_key:
225
+ specification_version: 3
226
+ summary: A simple lightweight state machine that uses an enum type to store states
227
+ test_files: []