simpler_state_machine 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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: []