state_shifter 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,16 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ # Add dependencies to develop your gem here.
7
+ # Include everything needed to run rake, tests, features, etc.
8
+ group :development do
9
+ gem "rspec", "~> 2.8.0"
10
+ gem "yard", "~> 0.7"
11
+ gem 'redcarpet'
12
+ gem "rdoc", "~> 3.12"
13
+ gem "bundler", "~> 1.1.0"
14
+ gem "jeweler", "~> 1.8.4"
15
+ gem "simplecov", ">= 0"
16
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,41 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ diff-lcs (1.1.3)
5
+ git (1.2.5)
6
+ jeweler (1.8.4)
7
+ bundler (~> 1.0)
8
+ git (>= 1.2.5)
9
+ rake
10
+ rdoc
11
+ json (1.7.5)
12
+ multi_json (1.3.6)
13
+ rake (0.9.2.2)
14
+ rdoc (3.12)
15
+ json (~> 1.4)
16
+ redcarpet (2.1.1)
17
+ rspec (2.8.0)
18
+ rspec-core (~> 2.8.0)
19
+ rspec-expectations (~> 2.8.0)
20
+ rspec-mocks (~> 2.8.0)
21
+ rspec-core (2.8.0)
22
+ rspec-expectations (2.8.0)
23
+ diff-lcs (~> 1.1.2)
24
+ rspec-mocks (2.8.0)
25
+ simplecov (0.6.4)
26
+ multi_json (~> 1.0)
27
+ simplecov-html (~> 0.5.3)
28
+ simplecov-html (0.5.3)
29
+ yard (0.8.2.1)
30
+
31
+ PLATFORMS
32
+ ruby
33
+
34
+ DEPENDENCIES
35
+ bundler (~> 1.1.0)
36
+ jeweler (~> 1.8.4)
37
+ rdoc (~> 3.12)
38
+ redcarpet
39
+ rspec (~> 2.8.0)
40
+ simplecov
41
+ yard (~> 0.7)
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Bruno Antunes
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,22 @@
1
+ state\_shifter
2
+ ==============
3
+
4
+ Description goes here.
5
+
6
+ Contributing to state\_shifter
7
+ ------------------------------
8
+
9
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
10
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
11
+ * Fork the project.
12
+ * Start a feature/bugfix branch.
13
+ * Commit and push until you are happy with your contribution.
14
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
15
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
16
+
17
+ Copyright
18
+ ---------
19
+
20
+ Copyright (c) 2012 Bruno Antunes. See LICENSE.txt for
21
+ further details.
22
+
data/Rakefile ADDED
@@ -0,0 +1,42 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "state_shifter"
18
+ gem.homepage = "http://github.com/sardaukar/state_shifter"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{state_shifter is a gem that adds state machine behavior to a class}
21
+ gem.description = %Q{state_shifter is a gem that adds state machine behavior to a class}
22
+ gem.email = "sardaukar.siet@gmail.com"
23
+ gem.authors = ["Bruno Antunes"]
24
+ # dependencies defined in Gemfile
25
+ end
26
+ Jeweler::RubygemsDotOrgTasks.new
27
+
28
+ require 'rspec/core'
29
+ require 'rspec/core/rake_task'
30
+ RSpec::Core::RakeTask.new(:spec) do |spec|
31
+ spec.pattern = FileList['spec/**/*_spec.rb']
32
+ end
33
+
34
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
35
+ ENV['COVERAGE'] = 'true'
36
+ spec.pattern = 'spec/**/*_spec.rb'
37
+ end
38
+
39
+ task :default => :rcov
40
+
41
+ require 'yard'
42
+ YARD::Rake::YardocTask.new
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.7.0
@@ -0,0 +1,95 @@
1
+ class Advanced
2
+ include StateShifter::Definition
3
+
4
+ ###
5
+
6
+ state_machine do
7
+
8
+ state :initialized do
9
+
10
+ event :start_date_changed, :call => :handle_start_date_changed
11
+ event :forced_start => :running
12
+ event :start_date_reached => :running, :if => :start_date_reached?
13
+ event :abort_initialized_contest => :finalized
14
+ end
15
+
16
+ state :running do
17
+
18
+ on_entry do |previous_state, trigger_event|
19
+ running_entry previous_state, trigger_event
20
+ end
21
+
22
+ event :abort_running_contest => :notify_stakeholders
23
+ event :changed_properties
24
+ event :keep_users_engaged
25
+ event :deadline_reached => :notify_organizers, :if => :entries_deadline_reached?
26
+ event :spots_filled => :notify_organizers, :if => :spots_filled?
27
+ event :deadline_reached_without_approvals => :notify_pending_users, :if => :entries_deadline_reached_without_approvals?
28
+ event :deadline_reached_without_entries => :finalized, :if => :entries_deadline_reached_without_entries?
29
+ end
30
+
31
+ state :notify_organizers do
32
+ on_entry :send_notification_to_organizers
33
+ event :organizers_notified => :awaiting_organizer_reply
34
+ end
35
+
36
+ state :awaiting_organizer_reply do
37
+ event :organizer_confirmation_missing => :notify_stakeholders, :if => :organizer_confirmation_deadline_reached?
38
+ event :keep_organizers_engaged
39
+ event :organizer_confirmation_received => :notify_approved_users
40
+ event :organizer_has_more_tickets => :running
41
+ end
42
+
43
+ state :notify_stakeholders do
44
+ on_entry :send_notification_to_stakeholders
45
+ event :stakeholders_notified => :cancelled
46
+ end
47
+
48
+ state :cancelled
49
+
50
+ state :notify_pending_users do
51
+ on_entry :send_notification_to_pending_users
52
+ event :pending_users_notified => :finalized
53
+ end
54
+
55
+ state :notify_approved_users do
56
+ on_entry :send_notification_to_approved_users
57
+ event :approved_users_notified => :send_list_to_organizers
58
+ end
59
+
60
+ state :send_list_to_organizers do
61
+ on_entry :send_guestlist_to_organizers
62
+ event :list_sent_to_organizers => :awaiting_attendance
63
+ end
64
+
65
+ state :awaiting_attendance do
66
+ event :remind_to_fill_in_report => :create_report_filling_requests
67
+ end
68
+
69
+ state :create_report_filling_requests do
70
+ on_entry :send_report_filling_requests
71
+ event :finalize => :finalized
72
+ end
73
+
74
+ state :finalized
75
+
76
+ on_transition do |from,to,trigger_event, duration|
77
+ benchmark from, to, trigger_event, duration
78
+ end
79
+ end
80
+
81
+ ###
82
+
83
+ def entries_deadline_reached?
84
+ true
85
+ end
86
+
87
+ def running_entry previous_state, trigger_event
88
+ #
89
+ end
90
+
91
+ def benchmark from, to, trigger_event, duration
92
+ #
93
+ end
94
+
95
+ end
@@ -0,0 +1,33 @@
1
+ class MalformedEvents
2
+ include StateShifter::Definition
3
+
4
+ state_machine do
5
+
6
+ # first state to be defined is the initial one
7
+ state :new do
8
+ event :submit => :awaiting_review
9
+ end
10
+
11
+ state :awaiting_review do
12
+ event :submit => :being_reviewed
13
+ end
14
+
15
+ state :being_reviewed do
16
+ event :accept => :accepted, :if => :cool_article?
17
+ event :reject => :rejected, :if => :bad_article?
18
+ end
19
+
20
+ state :accepted
21
+ state :rejected
22
+
23
+ end
24
+
25
+ def cool_article?
26
+ true
27
+ end
28
+
29
+ def bad_article?
30
+ false
31
+ end
32
+
33
+ end
@@ -0,0 +1,33 @@
1
+ class MalformedStates
2
+ include StateShifter::Definition
3
+
4
+ state_machine do
5
+
6
+ # first state to be defined is the initial one
7
+ state :new do
8
+ event :submit => :awaiting_review
9
+ end
10
+
11
+ state :new do
12
+ event :review => :being_reviewed
13
+ end
14
+
15
+ state :being_reviewed do
16
+ event :accept => :accepted, :if => :cool_article?
17
+ event :reject => :rejected, :if => :bad_article?
18
+ end
19
+
20
+ state :accepted
21
+ state :rejected
22
+
23
+ end
24
+
25
+ def cool_article?
26
+ true
27
+ end
28
+
29
+ def bad_article?
30
+ false
31
+ end
32
+
33
+ end
@@ -0,0 +1,8 @@
1
+ require_relative 'simple'
2
+
3
+ class Mounted
4
+ include StateShifter::Mountable
5
+
6
+ mount_state_machine Simple
7
+
8
+ end
@@ -0,0 +1,33 @@
1
+ class Simple
2
+ include StateShifter::Definition
3
+
4
+ state_machine do
5
+
6
+ # first state to be defined is the initial one
7
+ state :new do
8
+ event :submit => :awaiting_review
9
+ end
10
+
11
+ state :awaiting_review do
12
+ event :review => :being_reviewed
13
+ end
14
+
15
+ state :being_reviewed do
16
+ event :accept => :accepted, :if => :cool_article?
17
+ event :reject => :rejected, :if => :bad_article?
18
+ end
19
+
20
+ state :accepted
21
+ state :rejected
22
+
23
+ end
24
+
25
+ def cool_article?
26
+ true
27
+ end
28
+
29
+ def bad_article?
30
+ false
31
+ end
32
+
33
+ end
@@ -0,0 +1,84 @@
1
+ module StateShifter
2
+ module Definition
3
+ module ClassMethods
4
+
5
+ attr_accessor :state_machine_definition
6
+
7
+ def state_machine &definition
8
+ @state_machine_definition = Contents.new(&definition)
9
+ add_methods
10
+ end
11
+
12
+ def add_methods
13
+ @state_machine_definition.states.each do |state_name, state_definition|
14
+
15
+ module_eval do
16
+
17
+ define_method "_next_states" do |from_state, *options|
18
+ options = options.first || {}
19
+ next_states_hash = {}
20
+ check_guards = options.has_key?(:check_guards)
21
+
22
+ state_machine_definition.get(:state, from_state).events.each do |event_name, event_def|
23
+ if event_def.has_guards? && check_guards
24
+ next if @subject.send(:check_guards, event_name).is_a?(Array)
25
+ end
26
+
27
+ next_states_hash.merge!( event_def.to.nil? ? { from_state.to_sym => event_name } : { event_def.to => event_def.name } )
28
+ end
29
+
30
+ next_states_hash.keys.uniq.sort
31
+ end
32
+
33
+ define_method "#{state_name}?" do
34
+ current_state == state_name
35
+ end
36
+
37
+ state_machine_definition.events.each do |event_name, event_definition|
38
+
39
+ define_method "can_#{event_name}?" do
40
+
41
+ this_event = state_machine_definition.get(:event, event_name)
42
+
43
+ current_state == this_event.from && !check_guards(event_name).is_a?(Array)
44
+
45
+ end
46
+
47
+ define_method "#{event_name}!" do
48
+
49
+ self.send event_name.to_sym, true
50
+
51
+ end
52
+
53
+ define_method "#{event_name}" do |bang=false|
54
+
55
+ if current_state != event_definition.from
56
+ if bang
57
+ halt("you cannot transition from #{current_state} via #{event_name}")
58
+ else
59
+ return false
60
+ end
61
+ end
62
+
63
+ if (failed_guards = check_guards(event_name)).is_a?(Array)
64
+ if bang
65
+ failed_guards.delete_at(0)
66
+ raise ::StateShifter::GuardNotSatisfied, "#{failed_guards.join}"
67
+ else
68
+ return false
69
+ end
70
+ end
71
+
72
+ transition :to => ( event_definition.to.nil? ? current_state : event_definition.to ), :trigger => ( bang ? "#{event_name}!" : event_name )
73
+
74
+ end
75
+
76
+ end
77
+
78
+ end
79
+ end
80
+ end
81
+
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,85 @@
1
+ module StateShifter
2
+
3
+ module Definition
4
+
5
+ class Contents
6
+
7
+ attr_accessor :states, :initial_state, :events, :on_transition_proc
8
+
9
+ def initialize &definition
10
+ @states = {}
11
+ @events = {}
12
+ instance_eval &definition if block_given?
13
+ end
14
+
15
+ def state name, &events_and_stuff
16
+ this_state = State.new(name)
17
+ @initial_state = this_state if @states.empty? # first state declared is the initial one
18
+
19
+ raise ::StateShifter::RedifiningState, this_state.name if @states.has_key?(this_state.name.to_sym)
20
+
21
+ @states[this_state.name.to_sym] = this_state
22
+ @current_state = this_state
23
+ instance_eval &events_and_stuff if events_and_stuff
24
+ end
25
+
26
+ def event hash_or_sym, hash=nil
27
+ this_event =
28
+ if hash.nil?
29
+ if hash_or_sym.is_a?(Symbol)
30
+ # looping event
31
+ event_name = hash_or_sym
32
+
33
+ Event.new @current_state.name.to_sym, event_name
34
+ else
35
+ # normal event
36
+ event_guards = hash_or_sym.delete(:if)
37
+ event_name = hash_or_sym.keys.first
38
+ event_next_state = hash_or_sym[event_name.to_sym]
39
+
40
+ Event.new @current_state.name.to_sym, event_name, event_next_state, event_guards
41
+ end
42
+ else
43
+ event_guards = hash.delete(:if)
44
+ event_name = hash_or_sym
45
+ event_callback = hash.delete(:call)
46
+
47
+ Event.new @current_state.name.to_sym, event_name, @current_state.name.to_sym, event_guards, event_callback
48
+ end
49
+
50
+ raise ::StateShifter::RedifiningEvent, this_event.name if @events.has_key?(this_event.name.to_sym)
51
+
52
+ @events[this_event.name.to_sym] = this_event
53
+ @current_state.events[event_name.to_sym] = this_event
54
+ end
55
+
56
+ def on_entry event_name=nil, &proc_contents
57
+ if event_name.nil?
58
+ @current_state.entry_callback = proc_contents
59
+ else
60
+ @current_state.entry_callback = event_name
61
+ end
62
+ end
63
+
64
+ def get key, what
65
+ case key
66
+ when :event
67
+ @events[what.to_sym] || @events[what.to_s.gsub('!','').to_sym]
68
+ when :state
69
+ @states[what.to_sym] || @states[what.to_s.gsub('!','').to_sym]
70
+ end
71
+ end
72
+
73
+ def on_transition &proc_contents
74
+ @on_transition_proc = proc_contents
75
+ end
76
+
77
+ def has_on_transition_proc?
78
+ !@on_transition_proc.nil?
79
+ end
80
+
81
+ end
82
+
83
+ end
84
+
85
+ end
@@ -0,0 +1,109 @@
1
+ module StateShifter
2
+
3
+ module Definition
4
+
5
+ module InstanceMethods
6
+
7
+ attr_accessor :current_state, :state_machine_definition
8
+
9
+ def initialize
10
+ @current_state = state_machine_definition.initial_state.name.to_sym
11
+ @subject = self
12
+ end
13
+
14
+ def state_machine_definition
15
+ self.class.state_machine_definition
16
+ end
17
+
18
+ def next_states
19
+ _next_states current_state
20
+ end
21
+
22
+ def transitionable_states
23
+ _next_states current_state, {:check_guards => true}
24
+ end
25
+
26
+ def state_names
27
+ names_for :states
28
+ end
29
+
30
+ def event_names
31
+ names_for :events
32
+ end
33
+
34
+ def initial_state
35
+ state_machine_definition.initial_state.name
36
+ end
37
+
38
+ def names_for what
39
+ state_machine_definition.send(what).collect {|name, definition| name }
40
+ end
41
+
42
+ def check_event_callbacks event_name
43
+ event_def = state_machine_definition.get(:event, event_name)
44
+ begin
45
+ @subject.send event_def.callback
46
+ rescue NoMethodError
47
+ raise ::StateShifter::CallbackMethodNotDefined, event_def.callback
48
+ end
49
+ end
50
+
51
+ def current_state_def
52
+ state_machine_definition.get(:state, @current_state)
53
+ end
54
+
55
+ def call_state_entry_callback trigger, old_state
56
+ proc_or_method_name = current_state_def.entry_callback
57
+
58
+ if proc_or_method_name.is_a?(Symbol)
59
+ begin
60
+ @subject.send proc_or_method_name
61
+ rescue NoMethodError
62
+ raise ::StateShifter::CallbackMethodNotDefined, proc_or_method_name
63
+ end
64
+ else
65
+ @subject.instance_exec(old_state, trigger.to_sym, &proc_or_method_name)
66
+ end
67
+ end
68
+
69
+ def transition args
70
+ _start = Time.now
71
+
72
+ # BOOP!
73
+ old_state = @current_state
74
+ @current_state = args[:to].to_sym
75
+ #
76
+
77
+ check_event_callbacks(args[:trigger]) if state_machine_definition.get(:event, args[:trigger]).has_callback?
78
+
79
+ call_state_entry_callback(args[:trigger], old_state) if current_state_def.has_entry_callback?
80
+
81
+ @subject.instance_exec(old_state, @current_state, args[:trigger].to_sym, (Time.now - _start), &state_machine_definition.on_transition_proc) if state_machine_definition.has_on_transition_proc?
82
+ true
83
+ end
84
+
85
+ def halt message
86
+ raise ::StateShifter::TransitionHalted, message
87
+ end
88
+
89
+ def check_guards event_name
90
+ event = state_machine_definition.get(:event, event_name)
91
+
92
+ if event.has_guards?
93
+ event.guards.each do |guard|
94
+ begin
95
+ return false, guard unless self.send(guard.to_sym)
96
+ rescue NoMethodError
97
+ raise ::StateShifter::GuardMethodUndefined, guard
98
+ end
99
+ end
100
+ true
101
+ else
102
+ true
103
+ end
104
+ end
105
+
106
+ end
107
+ end
108
+
109
+ end
@@ -0,0 +1,10 @@
1
+ module StateShifter
2
+ module Definition
3
+
4
+ def self.included klass
5
+ klass.send :include, InstanceMethods
6
+ klass.extend ClassMethods
7
+ end
8
+
9
+ end
10
+ end
@@ -0,0 +1,22 @@
1
+ module StateShifter
2
+ class Event
3
+ attr_reader :name, :from, :to, :guards, :callback
4
+
5
+ def initialize from, name, to=nil, guards=nil, callback=nil
6
+ @name = name
7
+ @from = from
8
+ @to = to
9
+ @guards = [guards].flatten.compact
10
+ @callback = callback
11
+ end
12
+
13
+ def has_guards?
14
+ !@guards.nil?
15
+ end
16
+
17
+ def has_callback?
18
+ !@callback.nil?
19
+ end
20
+
21
+ end
22
+ end
@@ -0,0 +1,13 @@
1
+ module StateShifter
2
+ module Mountable
3
+ module ClassMethods
4
+
5
+ attr_accessor :mountable_class_name
6
+
7
+ def mount_state_machine mountable_class_name
8
+ self.mountable_class_name = mountable_class_name
9
+ end
10
+
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,20 @@
1
+ module StateShifter
2
+ module Mountable
3
+ module InstanceMethods
4
+
5
+ attr_accessor :state_machine_definition
6
+
7
+ def initialize
8
+ @state_machine_definition = self.class.mountable_class_name.new
9
+ @state_machine_definition.instance_variable_set(:@subject,self)
10
+
11
+ ( @state_machine_definition.methods - Object.methods).sort.each do |meth|
12
+ self.class.send :define_method, meth, &@state_machine_definition.method(meth)
13
+ end
14
+
15
+ super
16
+ end
17
+
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,10 @@
1
+ module StateShifter
2
+ module Mountable
3
+
4
+ def self.included klass
5
+ klass.send :include, InstanceMethods
6
+ klass.extend ClassMethods
7
+ end
8
+
9
+ end
10
+ end
@@ -0,0 +1,20 @@
1
+ module StateShifter
2
+
3
+ class State
4
+
5
+ attr_reader :name, :events
6
+ attr_accessor :entry_callback
7
+
8
+ def initialize name
9
+ @name = name
10
+ @events = {}
11
+ @entry_callback = nil
12
+ end
13
+
14
+ def has_entry_callback?
15
+ !@entry_callback.nil?
16
+ end
17
+
18
+ end
19
+
20
+ end
@@ -0,0 +1,17 @@
1
+ require 'state_shifter/state'
2
+ require 'state_shifter/event'
3
+ require 'state_shifter/definition'
4
+ require 'state_shifter/definition/contents'
5
+ require 'state_shifter/definition/class_methods'
6
+ require 'state_shifter/definition/instance_methods'
7
+ require 'state_shifter/mountable'
8
+ require 'state_shifter/mountable/class_methods'
9
+ require 'state_shifter/mountable/instance_methods'
10
+
11
+ class ::StateShifter::TransitionHalted < Exception ; end
12
+ class ::StateShifter::GuardMethodUndefined < Exception ; end
13
+ class ::StateShifter::GuardNotSatisfied < Exception ; end
14
+ class ::StateShifter::CallbackMethodNotDefined < Exception ; end
15
+ class ::StateShifter::RedifiningEvent < Exception ; end
16
+ class ::StateShifter::RedifiningState < Exception ; end
17
+
@@ -0,0 +1,19 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'rspec'
4
+ require 'state_shifter'
5
+ require 'simplecov'
6
+
7
+ if ENV['COVERAGE']
8
+ SimpleCov.start do
9
+ add_filter 'spec'
10
+ end
11
+ end
12
+
13
+ # Requires supporting files with custom matchers and macros, etc,
14
+ # in ./support/ and its subdirectories.
15
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
16
+
17
+ RSpec.configure do |config|
18
+
19
+ end
@@ -0,0 +1,162 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+ require_relative '../examples/simple'
3
+ require_relative '../examples/advanced'
4
+ require_relative '../examples/mounted'
5
+
6
+ shared_examples_for 'a simple state machine' do
7
+
8
+ before(:each) do
9
+ @state_machine = described_class.new
10
+ end
11
+
12
+ it 'should have all states and events defined' do
13
+ @state_machine.state_names.should == [:new, :awaiting_review, :being_reviewed, :accepted, :rejected]
14
+ @state_machine.event_names.should == [:submit, :review, :accept, :reject]
15
+ end
16
+
17
+ it 'should have the proper initial state set' do
18
+ @state_machine.initial_state.should == :new
19
+ @state_machine.current_state.should == :new
20
+
21
+ @state_machine.submit
22
+ @state_machine.initial_state.should == :new
23
+ end
24
+
25
+ it 'should transit between states properly' do
26
+ @state_machine.submit
27
+ @state_machine.current_state.should == :awaiting_review
28
+
29
+ @state_machine.review!
30
+ @state_machine.current_state.should == :being_reviewed
31
+ end
32
+
33
+ it 'should know the current state' do
34
+ @state_machine.current_state.should == :new
35
+
36
+ @state_machine.new?.should be_true
37
+ @state_machine.accepted?.should be_false
38
+
39
+ @state_machine.submit
40
+
41
+ @state_machine.awaiting_review?.should be_true
42
+ @state_machine.new?.should be_false
43
+ end
44
+
45
+ it 'should respect guard statements' do
46
+ @state_machine.submit
47
+ @state_machine.review
48
+
49
+ @state_machine.reject.should_not be_true
50
+ lambda { @state_machine.reject!}.should raise_error(StateShifter::GuardNotSatisfied, 'bad_article?')
51
+
52
+ @state_machine.accept.should be_true
53
+ @state_machine.current_state.should == :accepted
54
+ end
55
+
56
+ it 'should know if a transition is possible' do
57
+ @state_machine.can_submit?.should be_true
58
+
59
+ @state_machine.submit
60
+
61
+ @state_machine.can_review?.should be_true
62
+
63
+ @state_machine.review
64
+
65
+ @state_machine.can_accept?.should be_true
66
+ @state_machine.can_reject?.should be_false
67
+ end
68
+
69
+ it 'should properly indicate next states' do
70
+ @state_machine.next_states.should == [:awaiting_review]
71
+ @state_machine.transitionable_states.should == [:awaiting_review]
72
+
73
+ @state_machine.submit!
74
+ @state_machine.next_states.should == [:being_reviewed]
75
+ @state_machine.transitionable_states.should == [:being_reviewed]
76
+
77
+ @state_machine.review!
78
+ @state_machine.next_states.should == [:accepted, :rejected]
79
+ @state_machine.transitionable_states.should == [:accepted]
80
+
81
+ @state_machine.accept
82
+ @state_machine.next_states.should == []
83
+ @state_machine.transitionable_states.should == []
84
+ end
85
+
86
+ it 'should respect proper transition precedence' do
87
+ @state_machine.current_state.should == :new
88
+ lambda { @state_machine.review! }.should raise_error(StateShifter::TransitionHalted, 'you cannot transition from new via review')
89
+ end
90
+
91
+ end
92
+
93
+ describe Simple do
94
+
95
+ it_should_behave_like 'a simple state machine'
96
+
97
+ end
98
+
99
+ describe Mounted do
100
+
101
+ it_should_behave_like 'a simple state machine'
102
+
103
+ end
104
+
105
+ describe 'Malformed state machines' do
106
+
107
+ it 'should complain about redifining states' do
108
+ lambda { require_relative '../examples/malformed_states' }.should raise_error(StateShifter::RedifiningState, 'new')
109
+ end
110
+
111
+ it 'should complain about redifining events' do
112
+ lambda { require_relative '../examples/malformed_events' }.should raise_error(StateShifter::RedifiningEvent, 'submit')
113
+ end
114
+
115
+ it 'should complain about undefined guards' do
116
+ advanced = Advanced.new
117
+ lambda { advanced.can_start_date_reached?}.should raise_error(StateShifter::GuardMethodUndefined, 'start_date_reached?')
118
+ end
119
+
120
+ it 'should complain about undefined callbacks on state entry' do
121
+ advanced = Advanced.new
122
+ advanced.forced_start!
123
+
124
+ lambda { advanced.deadline_reached }.should raise_error(StateShifter::CallbackMethodNotDefined, 'send_notification_to_organizers')
125
+ end
126
+
127
+ end
128
+
129
+ describe 'Advanced state machine functionality' do
130
+
131
+ before(:each) do
132
+ @advanced = Advanced.new
133
+ end
134
+
135
+ it 'should complain about looping event callbacks not being defined' do
136
+ lambda { @advanced.start_date_changed }.should raise_error(StateShifter::CallbackMethodNotDefined, 'handle_start_date_changed')
137
+ end
138
+
139
+ it 'should call looping event callbacks' do
140
+ @advanced.stub!(:handle_start_date_changed)
141
+
142
+ @advanced.should_receive(:handle_start_date_changed)
143
+ @advanced.start_date_changed
144
+ end
145
+
146
+ it 'state on_entry callbacks should work' do
147
+ # block
148
+ @advanced.should_receive(:running_entry).with(:initialized, :forced_start).and_return(nil)
149
+ @advanced.forced_start
150
+
151
+ # method name only
152
+ @advanced.should_receive(:send_notification_to_organizers)
153
+ @advanced.deadline_reached!
154
+ end
155
+
156
+ it 'the on_transition callback should work' do
157
+ @advanced.should_receive(:benchmark).with(:initialized, :running, :forced_start!, an_instance_of(Float)).and_return(nil)
158
+ @advanced.forced_start!
159
+ end
160
+
161
+ end
162
+
@@ -0,0 +1,83 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "state_shifter"
8
+ s.version = "0.7.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Bruno Antunes"]
12
+ s.date = "2012-08-24"
13
+ s.description = "state_shifter is a gem that adds state machine behavior to a class"
14
+ s.email = "sardaukar.siet@gmail.com"
15
+ s.extra_rdoc_files = [
16
+ "LICENSE.txt",
17
+ "README.md"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".rspec",
22
+ "Gemfile",
23
+ "Gemfile.lock",
24
+ "LICENSE.txt",
25
+ "README.md",
26
+ "Rakefile",
27
+ "VERSION",
28
+ "examples/advanced.rb",
29
+ "examples/malformed_events.rb",
30
+ "examples/malformed_states.rb",
31
+ "examples/mounted.rb",
32
+ "examples/simple.rb",
33
+ "lib/state_shifter.rb",
34
+ "lib/state_shifter/definition.rb",
35
+ "lib/state_shifter/definition/class_methods.rb",
36
+ "lib/state_shifter/definition/contents.rb",
37
+ "lib/state_shifter/definition/instance_methods.rb",
38
+ "lib/state_shifter/event.rb",
39
+ "lib/state_shifter/mountable.rb",
40
+ "lib/state_shifter/mountable/class_methods.rb",
41
+ "lib/state_shifter/mountable/instance_methods.rb",
42
+ "lib/state_shifter/state.rb",
43
+ "spec/spec_helper.rb",
44
+ "spec/state_shifter_spec.rb",
45
+ "state_shifter.gemspec"
46
+ ]
47
+ s.homepage = "http://github.com/sardaukar/state_shifter"
48
+ s.licenses = ["MIT"]
49
+ s.require_paths = ["lib"]
50
+ s.rubygems_version = "1.8.24"
51
+ s.summary = "state_shifter is a gem that adds state machine behavior to a class"
52
+
53
+ if s.respond_to? :specification_version then
54
+ s.specification_version = 3
55
+
56
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
57
+ s.add_development_dependency(%q<rspec>, ["~> 2.8.0"])
58
+ s.add_development_dependency(%q<yard>, ["~> 0.7"])
59
+ s.add_development_dependency(%q<redcarpet>, [">= 0"])
60
+ s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
61
+ s.add_development_dependency(%q<bundler>, ["~> 1.1.0"])
62
+ s.add_development_dependency(%q<jeweler>, ["~> 1.8.4"])
63
+ s.add_development_dependency(%q<simplecov>, [">= 0"])
64
+ else
65
+ s.add_dependency(%q<rspec>, ["~> 2.8.0"])
66
+ s.add_dependency(%q<yard>, ["~> 0.7"])
67
+ s.add_dependency(%q<redcarpet>, [">= 0"])
68
+ s.add_dependency(%q<rdoc>, ["~> 3.12"])
69
+ s.add_dependency(%q<bundler>, ["~> 1.1.0"])
70
+ s.add_dependency(%q<jeweler>, ["~> 1.8.4"])
71
+ s.add_dependency(%q<simplecov>, [">= 0"])
72
+ end
73
+ else
74
+ s.add_dependency(%q<rspec>, ["~> 2.8.0"])
75
+ s.add_dependency(%q<yard>, ["~> 0.7"])
76
+ s.add_dependency(%q<redcarpet>, [">= 0"])
77
+ s.add_dependency(%q<rdoc>, ["~> 3.12"])
78
+ s.add_dependency(%q<bundler>, ["~> 1.1.0"])
79
+ s.add_dependency(%q<jeweler>, ["~> 1.8.4"])
80
+ s.add_dependency(%q<simplecov>, [">= 0"])
81
+ end
82
+ end
83
+
metadata ADDED
@@ -0,0 +1,188 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: state_shifter
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.7.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Bruno Antunes
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-08-24 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 2.8.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: 2.8.0
30
+ - !ruby/object:Gem::Dependency
31
+ name: yard
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: '0.7'
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.7'
46
+ - !ruby/object:Gem::Dependency
47
+ name: redcarpet
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: rdoc
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: '3.12'
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: '3.12'
78
+ - !ruby/object:Gem::Dependency
79
+ name: bundler
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ~>
84
+ - !ruby/object:Gem::Version
85
+ version: 1.1.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: 1.1.0
94
+ - !ruby/object:Gem::Dependency
95
+ name: jeweler
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ~>
100
+ - !ruby/object:Gem::Version
101
+ version: 1.8.4
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: 1.8.4
110
+ - !ruby/object:Gem::Dependency
111
+ name: simplecov
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
+ description: state_shifter is a gem that adds state machine behavior to a class
127
+ email: sardaukar.siet@gmail.com
128
+ executables: []
129
+ extensions: []
130
+ extra_rdoc_files:
131
+ - LICENSE.txt
132
+ - README.md
133
+ files:
134
+ - .document
135
+ - .rspec
136
+ - Gemfile
137
+ - Gemfile.lock
138
+ - LICENSE.txt
139
+ - README.md
140
+ - Rakefile
141
+ - VERSION
142
+ - examples/advanced.rb
143
+ - examples/malformed_events.rb
144
+ - examples/malformed_states.rb
145
+ - examples/mounted.rb
146
+ - examples/simple.rb
147
+ - lib/state_shifter.rb
148
+ - lib/state_shifter/definition.rb
149
+ - lib/state_shifter/definition/class_methods.rb
150
+ - lib/state_shifter/definition/contents.rb
151
+ - lib/state_shifter/definition/instance_methods.rb
152
+ - lib/state_shifter/event.rb
153
+ - lib/state_shifter/mountable.rb
154
+ - lib/state_shifter/mountable/class_methods.rb
155
+ - lib/state_shifter/mountable/instance_methods.rb
156
+ - lib/state_shifter/state.rb
157
+ - spec/spec_helper.rb
158
+ - spec/state_shifter_spec.rb
159
+ - state_shifter.gemspec
160
+ homepage: http://github.com/sardaukar/state_shifter
161
+ licenses:
162
+ - MIT
163
+ post_install_message:
164
+ rdoc_options: []
165
+ require_paths:
166
+ - lib
167
+ required_ruby_version: !ruby/object:Gem::Requirement
168
+ none: false
169
+ requirements:
170
+ - - ! '>='
171
+ - !ruby/object:Gem::Version
172
+ version: '0'
173
+ segments:
174
+ - 0
175
+ hash: 4081808216781371275
176
+ required_rubygems_version: !ruby/object:Gem::Requirement
177
+ none: false
178
+ requirements:
179
+ - - ! '>='
180
+ - !ruby/object:Gem::Version
181
+ version: '0'
182
+ requirements: []
183
+ rubyforge_project:
184
+ rubygems_version: 1.8.24
185
+ signing_key:
186
+ specification_version: 3
187
+ summary: state_shifter is a gem that adds state machine behavior to a class
188
+ test_files: []