transitions 0.0.4

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
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,24 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## GEM
22
+ *.gem
23
+
24
+ ## PROJECT::SPECIFIC
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Jakub Kuźma
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.rdoc ADDED
@@ -0,0 +1,27 @@
1
+ = state_machine
2
+
3
+ The gem is based on Rick Olson's code of ActiveModel::StateMachine,
4
+ axed from ActiveModel in {this
5
+ commit}[http://github.com/rails/rails/commit/db49c706b62e7ea2ab93f05399dbfddf5087ee0c].
6
+
7
+ == Installation
8
+
9
+ Currently there's no gem released on gemcutter (we need a different
10
+ name). However it's fully usable in bundler, you can just paste the
11
+ following line into the Gemfile:
12
+
13
+ gem "state_machine", :git => "git://github.com/qoobaa/state_machine.git"
14
+
15
+ == Note on Patches/Pull Requests
16
+
17
+ * Fork the project.
18
+ * Make your feature addition or bug fix.
19
+ * Add tests for it. This is important so I don't break it in a
20
+ future version unintentionally.
21
+ * Commit, do not mess with rakefile, version, or history.
22
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
23
+ * Send me a pull request. Bonus points for topic branches.
24
+
25
+ == Copyright
26
+
27
+ Copyright (c) 2010 Jakub Kuźma. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,58 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require "rubygems"
4
+ require "rake"
5
+
6
+ begin
7
+ require "jeweler"
8
+ Jeweler::Tasks.new do |gem|
9
+ gem.name = "transitions"
10
+ gem.summary = %Q{State machine extracted from ActiveModel}
11
+ # gem.description = %Q{TODO: longer description of your gem}
12
+ gem.email = "qoobaa@gmail.com"
13
+ gem.homepage = "http://github.com/qoobaa/transitions"
14
+ gem.authors = ["Jakub Kuźma"]
15
+ gem.add_development_dependency "test-unit", ">= 2"
16
+ gem.add_development_dependency "mocha"
17
+ gem.add_development_dependency "sqlite3-ruby"
18
+ gem.add_development_dependency "activerecord"
19
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
20
+ end
21
+ Jeweler::GemcutterTasks.new
22
+ rescue LoadError
23
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
24
+ end
25
+
26
+ require "rake/testtask"
27
+ Rake::TestTask.new(:test) do |test|
28
+ test.libs << "lib" << "test"
29
+ test.pattern = "test/**/test_*.rb"
30
+ test.verbose = true
31
+ end
32
+
33
+ begin
34
+ require "rcov/rcovtask"
35
+ Rcov::RcovTask.new do |test|
36
+ test.libs << "test"
37
+ test.pattern = "test/**/test_*.rb"
38
+ test.verbose = true
39
+ end
40
+ rescue LoadError
41
+ task :rcov do
42
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
43
+ end
44
+ end
45
+
46
+ task :test => :check_dependencies
47
+
48
+ task :default => :test
49
+
50
+ require "rake/rdoctask"
51
+ Rake::RDocTask.new do |rdoc|
52
+ version = File.exist?("VERSION") ? File.read("VERSION") : ""
53
+
54
+ rdoc.rdoc_dir = "rdoc"
55
+ rdoc.title = "state_machine #{version}"
56
+ rdoc.rdoc_files.include("README*")
57
+ rdoc.rdoc_files.include("lib/**/*.rb")
58
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.4
@@ -0,0 +1,47 @@
1
+ # Copyright (c) 2009 Rick Olson
2
+
3
+ # Permission is hereby granted, free of charge, to any person
4
+ # obtaining a copy of this software and associated documentation files
5
+ # (the "Software"), to deal in the Software without restriction,
6
+ # including without limitation the rights to use, copy, modify, merge,
7
+ # publish, distribute, sublicense, and/or sell copies of the Software,
8
+ # and to permit persons to whom the Software is furnished to do so,
9
+ # subject to 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
18
+ # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
19
+ # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ # SOFTWARE.
22
+
23
+ module ActiveRecord
24
+ module StateMachine
25
+ extend ActiveSupport::Concern
26
+
27
+ included do
28
+ include ::StateMachine
29
+ before_validation :set_initial_state
30
+ validates_presence_of :state
31
+ end
32
+
33
+ protected
34
+
35
+ def write_state(state_machine, state)
36
+ update_attributes! :state => state.to_s
37
+ end
38
+
39
+ def read_state(state_machine)
40
+ self.state.to_sym
41
+ end
42
+
43
+ def set_initial_state
44
+ self.state ||= self.class.state_machine.initial_state.to_s
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,93 @@
1
+ # Copyright (c) 2009 Rick Olson
2
+
3
+ # Permission is hereby granted, free of charge, to any person
4
+ # obtaining a copy of this software and associated documentation files
5
+ # (the "Software"), to deal in the Software without restriction,
6
+ # including without limitation the rights to use, copy, modify, merge,
7
+ # publish, distribute, sublicense, and/or sell copies of the Software,
8
+ # and to permit persons to whom the Software is furnished to do so,
9
+ # subject to 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
18
+ # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
19
+ # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ # SOFTWARE.
22
+
23
+ require "state_machine/event"
24
+ require "state_machine/machine"
25
+ require "state_machine/state"
26
+ require "state_machine/state_transition"
27
+
28
+ module StateMachine
29
+ class InvalidTransition < Exception
30
+
31
+ end
32
+
33
+ module ClassMethods
34
+ def inherited(klass)
35
+ super
36
+ klass.state_machines = state_machines
37
+ end
38
+
39
+ def state_machines
40
+ @state_machines ||= {}
41
+ end
42
+
43
+ def state_machines=(value)
44
+ @state_machines = value ? value.dup : nil
45
+ end
46
+
47
+ def state_machine(name = nil, options = {}, &block)
48
+ if name.is_a?(Hash)
49
+ options = name
50
+ name = nil
51
+ end
52
+ name ||= :default
53
+ state_machines[name] ||= Machine.new(self, name)
54
+ block ? state_machines[name].update(options, &block) : state_machines[name]
55
+ end
56
+
57
+ def define_state_query_method(state_name)
58
+ name = "#{state_name}?"
59
+ undef_method(name) if method_defined?(name)
60
+ class_eval "def #{name}; current_state.to_s == %(#{state_name}) end"
61
+ end
62
+ end
63
+
64
+ def self.included(base)
65
+ base.extend(ClassMethods)
66
+ end
67
+
68
+ def current_state(name = nil, new_state = nil, persist = false)
69
+ sm = self.class.state_machine(name)
70
+ ivar = sm.current_state_variable
71
+ if name && new_state
72
+ if persist && respond_to?(:write_state)
73
+ write_state(sm, new_state)
74
+ end
75
+
76
+ if respond_to?(:write_state_without_persistence)
77
+ write_state_without_persistence(sm, new_state)
78
+ end
79
+
80
+ instance_variable_set(ivar, new_state)
81
+ else
82
+ instance_variable_set(ivar, nil) unless instance_variable_defined?(ivar)
83
+ value = instance_variable_get(ivar)
84
+ return value if value
85
+
86
+ if respond_to?(:read_state)
87
+ value = instance_variable_set(ivar, read_state(sm))
88
+ end
89
+
90
+ value || sm.initial_state
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,83 @@
1
+ # Copyright (c) 2009 Rick Olson
2
+
3
+ # Permission is hereby granted, free of charge, to any person
4
+ # obtaining a copy of this software and associated documentation files
5
+ # (the "Software"), to deal in the Software without restriction,
6
+ # including without limitation the rights to use, copy, modify, merge,
7
+ # publish, distribute, sublicense, and/or sell copies of the Software,
8
+ # and to permit persons to whom the Software is furnished to do so,
9
+ # subject to 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
18
+ # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
19
+ # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ # SOFTWARE.
22
+
23
+ module StateMachine
24
+ class Event
25
+ attr_reader :name, :success
26
+
27
+ def initialize(machine, name, options = {}, &block)
28
+ @machine, @name, @transitions = machine, name, []
29
+ if machine
30
+ machine.klass.send(:define_method, "#{name}!") do |*args|
31
+ machine.fire_event(name, self, true, *args)
32
+ end
33
+
34
+ machine.klass.send(:define_method, name.to_s) do |*args|
35
+ machine.fire_event(name, self, false, *args)
36
+ end
37
+ end
38
+ update(options, &block)
39
+ end
40
+
41
+ def fire(obj, to_state = nil, *args)
42
+ transitions = @transitions.select { |t| t.from == obj.current_state(@machine ? @machine.name : nil) }
43
+ raise InvalidTransition if transitions.size == 0
44
+
45
+ next_state = nil
46
+ transitions.each do |transition|
47
+ next if to_state && !Array(transition.to).include?(to_state)
48
+ if transition.perform(obj)
49
+ next_state = to_state || Array(transition.to).first
50
+ transition.execute(obj, *args)
51
+ break
52
+ end
53
+ end
54
+ next_state
55
+ end
56
+
57
+ def transitions_from_state?(state)
58
+ @transitions.any? { |t| t.from? state }
59
+ end
60
+
61
+ def ==(event)
62
+ if event.is_a? Symbol
63
+ name == event
64
+ else
65
+ name == event.name
66
+ end
67
+ end
68
+
69
+ def update(options = {}, &block)
70
+ @success = options[:success] if options.key?(:success)
71
+ instance_eval(&block) if block
72
+ self
73
+ end
74
+
75
+ private
76
+
77
+ def transitions(trans_opts)
78
+ Array(trans_opts[:from]).each do |s|
79
+ @transitions << StateTransition.new(trans_opts.merge({:from => s.to_sym}))
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,97 @@
1
+ # Copyright (c) 2009 Rick Olson
2
+
3
+ # Permission is hereby granted, free of charge, to any person
4
+ # obtaining a copy of this software and associated documentation files
5
+ # (the "Software"), to deal in the Software without restriction,
6
+ # including without limitation the rights to use, copy, modify, merge,
7
+ # publish, distribute, sublicense, and/or sell copies of the Software,
8
+ # and to permit persons to whom the Software is furnished to do so,
9
+ # subject to 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
18
+ # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
19
+ # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ # SOFTWARE.
22
+
23
+ module StateMachine
24
+ class Machine
25
+ attr_writer :initial_state
26
+ attr_accessor :states, :events, :state_index
27
+ attr_reader :klass, :name
28
+
29
+ def initialize(klass, name, options = {}, &block)
30
+ @klass, @name, @states, @state_index, @events = klass, name, [], {}, {}
31
+ update(options, &block)
32
+ end
33
+
34
+ def initial_state
35
+ @initial_state ||= (states.first ? states.first.name : nil)
36
+ end
37
+
38
+ def update(options = {}, &block)
39
+ @initial_state = options[:initial] if options.key?(:initial)
40
+ instance_eval(&block) if block
41
+ self
42
+ end
43
+
44
+ def fire_event(event, record, persist, *args)
45
+ state_index[record.current_state(@name)].call_action(:exit, record)
46
+ if new_state = @events[event].fire(record, nil, *args)
47
+ state_index[new_state].call_action(:enter, record)
48
+
49
+ if record.respond_to?(event_fired_callback)
50
+ record.send(event_fired_callback, record.current_state, new_state)
51
+ end
52
+
53
+ record.current_state(@name, new_state, persist)
54
+ record.send(@events[event].success) if @events[event].success
55
+ true
56
+ else
57
+ if record.respond_to?(event_failed_callback)
58
+ record.send(event_failed_callback, event)
59
+ end
60
+
61
+ false
62
+ end
63
+ end
64
+
65
+ def states_for_select
66
+ states.map { |st| [st.display_name, st.name.to_s] }
67
+ end
68
+
69
+ def events_for(state)
70
+ events = @events.values.select { |event| event.transitions_from_state?(state) }
71
+ events.map! { |event| event.name }
72
+ end
73
+
74
+ def current_state_variable
75
+ "@#{@name}_current_state"
76
+ end
77
+
78
+ private
79
+
80
+ def state(name, options = {})
81
+ @states << (state_index[name] ||= State.new(name, :machine => self)).update(options)
82
+ end
83
+
84
+ def event(name, options = {}, &block)
85
+ (@events[name] ||= Event.new(self, name)).update(options, &block)
86
+ end
87
+
88
+ def event_fired_callback
89
+ @event_fired_callback ||= (@name == :default ? '' : "#{@name}_") + 'event_fired'
90
+ end
91
+
92
+ def event_failed_callback
93
+ @event_failed_callback ||= (@name == :default ? '' : "#{@name}_") + 'event_failed'
94
+ end
95
+ end
96
+ end
97
+
@@ -0,0 +1,67 @@
1
+ # Copyright (c) 2009 Rick Olson
2
+
3
+ # Permission is hereby granted, free of charge, to any person
4
+ # obtaining a copy of this software and associated documentation files
5
+ # (the "Software"), to deal in the Software without restriction,
6
+ # including without limitation the rights to use, copy, modify, merge,
7
+ # publish, distribute, sublicense, and/or sell copies of the Software,
8
+ # and to permit persons to whom the Software is furnished to do so,
9
+ # subject to 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
18
+ # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
19
+ # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ # SOFTWARE.
22
+
23
+ module StateMachine
24
+ class State
25
+ attr_reader :name, :options
26
+
27
+ def initialize(name, options = {})
28
+ @name = name
29
+ if machine = options.delete(:machine)
30
+ machine.klass.define_state_query_method(name)
31
+ end
32
+ update(options)
33
+ end
34
+
35
+ def ==(state)
36
+ if state.is_a? Symbol
37
+ name == state
38
+ else
39
+ name == state.name
40
+ end
41
+ end
42
+
43
+ def call_action(action, record)
44
+ action = @options[action]
45
+ case action
46
+ when Symbol, String
47
+ record.send(action)
48
+ when Proc
49
+ action.call(record)
50
+ end
51
+ end
52
+
53
+ def display_name
54
+ @display_name ||= name.to_s.gsub(/_/, ' ').capitalize
55
+ end
56
+
57
+ def for_select
58
+ [display_name, name.to_s]
59
+ end
60
+
61
+ def update(options = {})
62
+ @display_name = options.delete(:display) if options.key?(:display)
63
+ @options = options
64
+ self
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,60 @@
1
+ # Copyright (c) 2009 Rick Olson
2
+
3
+ # Permission is hereby granted, free of charge, to any person
4
+ # obtaining a copy of this software and associated documentation files
5
+ # (the "Software"), to deal in the Software without restriction,
6
+ # including without limitation the rights to use, copy, modify, merge,
7
+ # publish, distribute, sublicense, and/or sell copies of the Software,
8
+ # and to permit persons to whom the Software is furnished to do so,
9
+ # subject to 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
18
+ # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
19
+ # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ # SOFTWARE.
22
+
23
+ module StateMachine
24
+ class StateTransition
25
+ attr_reader :from, :to, :options
26
+
27
+ def initialize(opts)
28
+ @from, @to, @guard, @on_transition = opts[:from], opts[:to], opts[:guard], opts[:on_transition]
29
+ @options = opts
30
+ end
31
+
32
+ def perform(obj)
33
+ case @guard
34
+ when Symbol, String
35
+ obj.send(@guard)
36
+ when Proc
37
+ @guard.call(obj)
38
+ else
39
+ true
40
+ end
41
+ end
42
+
43
+ def execute(obj, *args)
44
+ case @on_transition
45
+ when Symbol, String
46
+ obj.send(@on_transition, *args)
47
+ when Proc
48
+ @on_transition.call(obj, *args)
49
+ end
50
+ end
51
+
52
+ def ==(obj)
53
+ @from == obj.from && @to == obj.to
54
+ end
55
+
56
+ def from?(value)
57
+ @from == value
58
+ end
59
+ end
60
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,14 @@
1
+ require "rubygems"
2
+ gem "test-unit" # enforce gem usage on 1.8.x
3
+ require "test/unit"
4
+ require "active_record"
5
+ require "mocha"
6
+
7
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
8
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
9
+ require "state_machine"
10
+ require "active_record/state_machine"
11
+
12
+ class Test::Unit::TestCase
13
+
14
+ end
@@ -0,0 +1,79 @@
1
+ require "helper"
2
+
3
+ class CreateTrafficLights < ActiveRecord::Migration
4
+ def self.up
5
+ create_table(:traffic_lights) { |t| t.string :state }
6
+ end
7
+ end
8
+
9
+ class TrafficLight < ActiveRecord::Base
10
+ include ActiveRecord::StateMachine
11
+
12
+ state_machine do
13
+ state :off
14
+
15
+ state :red
16
+ state :green
17
+ state :yellow
18
+
19
+ event :red_on do
20
+ transitions :to => :red, :from => [:yellow]
21
+ end
22
+
23
+ event :green_on do
24
+ transitions :to => :green, :from => [:red]
25
+ end
26
+
27
+ event :yellow_on do
28
+ transitions :to => :yellow, :from => [:green]
29
+ end
30
+
31
+ event :reset do
32
+ transitions :to => :red, :from => [:off]
33
+ end
34
+ end
35
+ end
36
+
37
+ class TestActiveRecord < Test::Unit::TestCase
38
+ def setup
39
+ ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
40
+ ActiveRecord::Migration.verbose = false
41
+ CreateTrafficLights.migrate(:up)
42
+
43
+ @light = TrafficLight.create!
44
+ end
45
+
46
+ test "states initial state" do
47
+ assert @light.off?
48
+ assert_equal :off, @light.current_state
49
+ end
50
+
51
+ test "transition to a valid state" do
52
+ @light.reset
53
+ assert @light.red?
54
+ assert_equal :red, @light.current_state
55
+
56
+ @light.green_on
57
+ assert @light.green?
58
+ assert_equal :green, @light.current_state
59
+ end
60
+
61
+ test "transition does not persist state" do
62
+ @light.reset
63
+ assert_equal :red, @light.current_state
64
+ @light.reload
65
+ assert_equal "off", @light.state
66
+ end
67
+
68
+ test "transition does persists state" do
69
+ @light.reset!
70
+ assert_equal :red, @light.current_state
71
+ @light.reload
72
+ assert_equal "red", @light.state
73
+ end
74
+
75
+ test "transition to an invalid state" do
76
+ assert_raise(StateMachine::InvalidTransition) { @light.yellow_on }
77
+ assert_equal :off, @light.current_state
78
+ end
79
+ end
@@ -0,0 +1,28 @@
1
+ require "helper"
2
+
3
+ class TestEvent < Test::Unit::TestCase
4
+ def setup
5
+ @state_name = :close_order
6
+ @success = :success_callback
7
+ end
8
+
9
+ def new_event
10
+ @event = StateMachine::Event.new(nil, @state_name, {:success => @success}) do
11
+ transitions :to => :closed, :from => [:open, :received]
12
+ end
13
+ end
14
+
15
+ test "should set the name" do
16
+ assert_equal @state_name, new_event.name
17
+ end
18
+
19
+ test "should set the success option" do
20
+ assert_equal @success, new_event.success
21
+ end
22
+
23
+ test "should create StateTransitions" do
24
+ StateMachine::StateTransition.expects(:new).with(:to => :closed, :from => :open)
25
+ StateMachine::StateTransition.expects(:new).with(:to => :closed, :from => :received)
26
+ new_event
27
+ end
28
+ end
@@ -0,0 +1,30 @@
1
+ require "helper"
2
+
3
+ class ArgumentsTestSubject
4
+ include StateMachine
5
+ attr_accessor :date
6
+
7
+ state_machine do
8
+ state :initial
9
+ state :opened
10
+
11
+ event :open do
12
+ transitions :from => :initial, :to => :opened, :on_transition => :update_date
13
+ end
14
+ end
15
+
16
+ def update_date(date = Date.today)
17
+ self.date = date
18
+ end
19
+ end
20
+
21
+ class StateMachineMachineTest < Test::Unit::TestCase
22
+ test "pass arguments to transition method" do
23
+ subject = ArgumentsTestSubject.new
24
+ assert_equal :initial, subject.current_state
25
+ subject.open!(Date.yesterday)
26
+ assert_equal :opened, subject.current_state
27
+ assert_equal Date.yesterday, subject.date
28
+ end
29
+ end
30
+
@@ -0,0 +1,22 @@
1
+ require "helper"
2
+
3
+ class TestEventBeingFired < Test::Unit::TestCase
4
+ test "should raise an StateMachine::InvalidTransition error if the transitions are empty" do
5
+ event = StateMachine::Event.new(nil, :event)
6
+
7
+ assert_raise StateMachine::InvalidTransition do
8
+ event.fire(nil)
9
+ end
10
+ end
11
+
12
+ test "should return the state of the first matching transition it finds" do
13
+ event = StateMachine::Event.new(nil, :event) do
14
+ transitions :to => :closed, :from => [:open, :received]
15
+ end
16
+
17
+ obj = stub
18
+ obj.stubs(:current_state).returns(:open)
19
+
20
+ assert_equal :closed, event.fire(obj)
21
+ end
22
+ end
@@ -0,0 +1,43 @@
1
+ require "helper"
2
+
3
+ class MachineTestSubject
4
+ include StateMachine
5
+
6
+ state_machine do
7
+ state :open
8
+ state :closed
9
+ end
10
+
11
+ state_machine :initial => :foo do
12
+ event :shutdown do
13
+ transitions :from => :open, :to => :closed
14
+ end
15
+
16
+ event :timeout do
17
+ transitions :from => :open, :to => :closed
18
+ end
19
+ end
20
+
21
+ state_machine :extra, :initial => :bar do
22
+ end
23
+ end
24
+
25
+ class StateMachineMachineTest < Test::Unit::TestCase
26
+ test "allows reuse of existing machines" do
27
+ assert_equal 2, MachineTestSubject.state_machines.size
28
+ end
29
+
30
+ test "sets #initial_state from :initial option" do
31
+ assert_equal :bar, MachineTestSubject.state_machine(:extra).initial_state
32
+ end
33
+
34
+ test "accesses non-default state machine" do
35
+ assert_kind_of StateMachine::Machine, MachineTestSubject.state_machine(:extra)
36
+ end
37
+
38
+ test "finds events for given state" do
39
+ events = MachineTestSubject.state_machine.events_for(:open)
40
+ assert events.include?(:shutdown)
41
+ assert events.include?(:timeout)
42
+ end
43
+ end
@@ -0,0 +1,72 @@
1
+ require "helper"
2
+
3
+ class StateTestSubject
4
+ include StateMachine
5
+
6
+ state_machine do
7
+ end
8
+ end
9
+
10
+ class TestState < Test::Unit::TestCase
11
+ def setup
12
+ @state_name = :astate
13
+ @machine = StateTestSubject.state_machine
14
+ @options = { :crazy_custom_key => "key", :machine => @machine }
15
+ end
16
+
17
+ def new_state(options={})
18
+ StateMachine::State.new(@state_name, @options.merge(options))
19
+ end
20
+
21
+ test "sets the name" do
22
+ assert_equal :astate, new_state.name
23
+ end
24
+
25
+ test "sets the display_name from name" do
26
+ assert_equal "Astate", new_state.display_name
27
+ end
28
+
29
+ test "sets the display_name from options" do
30
+ assert_equal "A State", new_state(:display => "A State").display_name
31
+ end
32
+
33
+ test "sets the options and expose them as options" do
34
+ @options.delete(:machine)
35
+ assert_equal @options, new_state.options
36
+ end
37
+
38
+ test "equals a symbol of the same name" do
39
+ assert_equal new_state, :astate
40
+ end
41
+
42
+ test "equals a State of the same name" do
43
+ assert_equal new_state, new_state
44
+ end
45
+
46
+ test "should send a message to the record for an action if the action is present as a symbol" do
47
+ state = new_state(:entering => :foo)
48
+
49
+ record = stub
50
+ record.expects(:foo)
51
+
52
+ state.call_action(:entering, record)
53
+ end
54
+
55
+ test "should send a message to the record for an action if the action is present as a string" do
56
+ state = new_state(:entering => "foo")
57
+
58
+ record = stub
59
+ record.expects(:foo)
60
+
61
+ state.call_action(:entering, record)
62
+ end
63
+
64
+ test "should call a proc, passing in the record for an action if the action is present" do
65
+ state = new_state(:entering => Proc.new {|r| r.foobar})
66
+
67
+ record = stub
68
+ record.expects(:foobar)
69
+
70
+ state.call_action(:entering, record)
71
+ end
72
+ end
@@ -0,0 +1,45 @@
1
+ require "helper"
2
+
3
+ class TestStateTransition < Test::Unit::TestCase
4
+ test "should set from, to, and opts attr readers" do
5
+ opts = {:from => "foo", :to => "bar", :guard => "g"}
6
+ st = StateMachine::StateTransition.new(opts)
7
+
8
+ assert_equal opts[:from], st.from
9
+ assert_equal opts[:to], st.to
10
+ assert_equal opts, st.options
11
+ end
12
+
13
+ test "should pass equality check if from and to are the same" do
14
+ opts = {:from => "foo", :to => "bar", :guard => "g"}
15
+ st = StateMachine::StateTransition.new(opts)
16
+
17
+ obj = stub
18
+ obj.stubs(:from).returns(opts[:from])
19
+ obj.stubs(:to).returns(opts[:to])
20
+
21
+ assert_equal st, obj
22
+ end
23
+
24
+ test "should fail equality check if from are not the same" do
25
+ opts = {:from => "foo", :to => "bar", :guard => "g"}
26
+ st = StateMachine::StateTransition.new(opts)
27
+
28
+ obj = stub
29
+ obj.stubs(:from).returns("blah")
30
+ obj.stubs(:to).returns(opts[:to])
31
+
32
+ assert_not_equal st, obj
33
+ end
34
+
35
+ test "should fail equality check if to are not the same" do
36
+ opts = {:from => "foo", :to => "bar", :guard => "g"}
37
+ st = StateMachine::StateTransition.new(opts)
38
+
39
+ obj = stub
40
+ obj.stubs(:from).returns(opts[:from])
41
+ obj.stubs(:to).returns("blah")
42
+
43
+ assert_not_equal st, obj
44
+ end
45
+ end
@@ -0,0 +1,40 @@
1
+ require "helper"
2
+
3
+ class TestStateTransitionGuardCheck < Test::Unit::TestCase
4
+ test "should return true of there is no guard" do
5
+ opts = {:from => "foo", :to => "bar"}
6
+ st = StateMachine::StateTransition.new(opts)
7
+
8
+ assert st.perform(nil)
9
+ end
10
+
11
+ test "should call the method on the object if guard is a symbol" do
12
+ opts = {:from => "foo", :to => "bar", :guard => :test_guard}
13
+ st = StateMachine::StateTransition.new(opts)
14
+
15
+ obj = stub
16
+ obj.expects(:test_guard)
17
+
18
+ st.perform(obj)
19
+ end
20
+
21
+ test "should call the method on the object if guard is a string" do
22
+ opts = {:from => "foo", :to => "bar", :guard => "test_guard"}
23
+ st = StateMachine::StateTransition.new(opts)
24
+
25
+ obj = stub
26
+ obj.expects(:test_guard)
27
+
28
+ st.perform(obj)
29
+ end
30
+
31
+ test "should call the proc passing the object if the guard is a proc" do
32
+ opts = {:from => "foo", :to => "bar", :guard => Proc.new {|o| o.test_guard}}
33
+ st = StateMachine::StateTransition.new(opts)
34
+
35
+ obj = stub
36
+ obj.expects(:test_guard)
37
+
38
+ st.perform(obj)
39
+ end
40
+ end
@@ -0,0 +1,81 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{transitions}
8
+ s.version = "0.0.4"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Jakub Kuźma"]
12
+ s.date = %q{2010-03-14}
13
+ s.email = %q{qoobaa@gmail.com}
14
+ s.extra_rdoc_files = [
15
+ "LICENSE",
16
+ "README.rdoc"
17
+ ]
18
+ s.files = [
19
+ ".document",
20
+ ".gitignore",
21
+ "LICENSE",
22
+ "README.rdoc",
23
+ "Rakefile",
24
+ "VERSION",
25
+ "lib/active_record/state_machine.rb",
26
+ "lib/state_machine.rb",
27
+ "lib/state_machine/event.rb",
28
+ "lib/state_machine/machine.rb",
29
+ "lib/state_machine/state.rb",
30
+ "lib/state_machine/state_transition.rb",
31
+ "test/helper.rb",
32
+ "test/test_active_record.rb",
33
+ "test/test_event.rb",
34
+ "test/test_event_arguments.rb",
35
+ "test/test_event_being_fired.rb",
36
+ "test/test_machine.rb",
37
+ "test/test_state.rb",
38
+ "test/test_state_transition.rb",
39
+ "test/test_state_transition_guard_check.rb",
40
+ "transitions.gemspec"
41
+ ]
42
+ s.homepage = %q{http://github.com/qoobaa/transitions}
43
+ s.rdoc_options = ["--charset=UTF-8"]
44
+ s.require_paths = ["lib"]
45
+ s.rubygems_version = %q{1.3.6}
46
+ s.summary = %q{State machine extracted from ActiveModel}
47
+ s.test_files = [
48
+ "test/helper.rb",
49
+ "test/test_state_transition_guard_check.rb",
50
+ "test/test_active_record.rb",
51
+ "test/test_machine.rb",
52
+ "test/test_state_transition.rb",
53
+ "test/test_event_arguments.rb",
54
+ "test/test_state.rb",
55
+ "test/test_event_being_fired.rb",
56
+ "test/test_event.rb"
57
+ ]
58
+
59
+ if s.respond_to? :specification_version then
60
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
61
+ s.specification_version = 3
62
+
63
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
64
+ s.add_development_dependency(%q<test-unit>, [">= 2"])
65
+ s.add_development_dependency(%q<mocha>, [">= 0"])
66
+ s.add_development_dependency(%q<sqlite3-ruby>, [">= 0"])
67
+ s.add_development_dependency(%q<activerecord>, [">= 0"])
68
+ else
69
+ s.add_dependency(%q<test-unit>, [">= 2"])
70
+ s.add_dependency(%q<mocha>, [">= 0"])
71
+ s.add_dependency(%q<sqlite3-ruby>, [">= 0"])
72
+ s.add_dependency(%q<activerecord>, [">= 0"])
73
+ end
74
+ else
75
+ s.add_dependency(%q<test-unit>, [">= 2"])
76
+ s.add_dependency(%q<mocha>, [">= 0"])
77
+ s.add_dependency(%q<sqlite3-ruby>, [">= 0"])
78
+ s.add_dependency(%q<activerecord>, [">= 0"])
79
+ end
80
+ end
81
+
metadata ADDED
@@ -0,0 +1,139 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: transitions
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 4
9
+ version: 0.0.4
10
+ platform: ruby
11
+ authors:
12
+ - "Jakub Ku\xC5\xBAma"
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-03-14 00:00:00 +01:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: test-unit
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 2
29
+ version: "2"
30
+ type: :development
31
+ version_requirements: *id001
32
+ - !ruby/object:Gem::Dependency
33
+ name: mocha
34
+ prerelease: false
35
+ requirement: &id002 !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ segments:
40
+ - 0
41
+ version: "0"
42
+ type: :development
43
+ version_requirements: *id002
44
+ - !ruby/object:Gem::Dependency
45
+ name: sqlite3-ruby
46
+ prerelease: false
47
+ requirement: &id003 !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ segments:
52
+ - 0
53
+ version: "0"
54
+ type: :development
55
+ version_requirements: *id003
56
+ - !ruby/object:Gem::Dependency
57
+ name: activerecord
58
+ prerelease: false
59
+ requirement: &id004 !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ segments:
64
+ - 0
65
+ version: "0"
66
+ type: :development
67
+ version_requirements: *id004
68
+ description:
69
+ email: qoobaa@gmail.com
70
+ executables: []
71
+
72
+ extensions: []
73
+
74
+ extra_rdoc_files:
75
+ - LICENSE
76
+ - README.rdoc
77
+ files:
78
+ - .document
79
+ - .gitignore
80
+ - LICENSE
81
+ - README.rdoc
82
+ - Rakefile
83
+ - VERSION
84
+ - lib/active_record/state_machine.rb
85
+ - lib/state_machine.rb
86
+ - lib/state_machine/event.rb
87
+ - lib/state_machine/machine.rb
88
+ - lib/state_machine/state.rb
89
+ - lib/state_machine/state_transition.rb
90
+ - test/helper.rb
91
+ - test/test_active_record.rb
92
+ - test/test_event.rb
93
+ - test/test_event_arguments.rb
94
+ - test/test_event_being_fired.rb
95
+ - test/test_machine.rb
96
+ - test/test_state.rb
97
+ - test/test_state_transition.rb
98
+ - test/test_state_transition_guard_check.rb
99
+ - transitions.gemspec
100
+ has_rdoc: true
101
+ homepage: http://github.com/qoobaa/transitions
102
+ licenses: []
103
+
104
+ post_install_message:
105
+ rdoc_options:
106
+ - --charset=UTF-8
107
+ require_paths:
108
+ - lib
109
+ required_ruby_version: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ segments:
114
+ - 0
115
+ version: "0"
116
+ required_rubygems_version: !ruby/object:Gem::Requirement
117
+ requirements:
118
+ - - ">="
119
+ - !ruby/object:Gem::Version
120
+ segments:
121
+ - 0
122
+ version: "0"
123
+ requirements: []
124
+
125
+ rubyforge_project:
126
+ rubygems_version: 1.3.6
127
+ signing_key:
128
+ specification_version: 3
129
+ summary: State machine extracted from ActiveModel
130
+ test_files:
131
+ - test/helper.rb
132
+ - test/test_state_transition_guard_check.rb
133
+ - test/test_active_record.rb
134
+ - test/test_machine.rb
135
+ - test/test_state_transition.rb
136
+ - test/test_event_arguments.rb
137
+ - test/test_state.rb
138
+ - test/test_event_being_fired.rb
139
+ - test/test_event.rb