dm-is-state_machine 0.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/History.txt +1 -0
- data/LICENSE +20 -0
- data/Manifest.txt +31 -0
- data/README.txt +12 -0
- data/Rakefile +51 -0
- data/TODO +11 -0
- data/lib/dm-is-state_machine.rb +27 -0
- data/lib/dm-is-state_machine/is/data/event.rb +25 -0
- data/lib/dm-is-state_machine/is/data/machine.rb +69 -0
- data/lib/dm-is-state_machine/is/data/state.rb +21 -0
- data/lib/dm-is-state_machine/is/dsl/event_dsl.rb +73 -0
- data/lib/dm-is-state_machine/is/dsl/state_dsl.rb +40 -0
- data/lib/dm-is-state_machine/is/state_machine.rb +107 -0
- data/lib/dm-is-state_machine/is/version.rb +7 -0
- data/spec/examples/invalid_events.rb +20 -0
- data/spec/examples/invalid_states.rb +20 -0
- data/spec/examples/invalid_transitions_1.rb +22 -0
- data/spec/examples/invalid_transitions_2.rb +22 -0
- data/spec/examples/traffic_light.rb +44 -0
- data/spec/integration/invalid_events_spec.rb +12 -0
- data/spec/integration/invalid_states_spec.rb +12 -0
- data/spec/integration/invalid_transitions_spec.rb +22 -0
- data/spec/integration/traffic_light_spec.rb +150 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +28 -0
- data/spec/unit/data/event_spec.rb +28 -0
- data/spec/unit/data/machine_spec.rb +97 -0
- data/spec/unit/data/state_spec.rb +22 -0
- data/spec/unit/dsl/event_dsl_spec.rb +56 -0
- data/spec/unit/dsl/state_dsl_spec.rb +25 -0
- data/spec/unit/state_machine_spec.rb +34 -0
- metadata +106 -0
data/History.txt
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2008 David James
|
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/Manifest.txt
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
History.txt
|
2
|
+
LICENSE
|
3
|
+
Manifest.txt
|
4
|
+
README.txt
|
5
|
+
Rakefile
|
6
|
+
TODO
|
7
|
+
lib/dm-is-state_machine.rb
|
8
|
+
lib/dm-is-state_machine/is/data/event.rb
|
9
|
+
lib/dm-is-state_machine/is/data/machine.rb
|
10
|
+
lib/dm-is-state_machine/is/data/state.rb
|
11
|
+
lib/dm-is-state_machine/is/dsl/event_dsl.rb
|
12
|
+
lib/dm-is-state_machine/is/dsl/state_dsl.rb
|
13
|
+
lib/dm-is-state_machine/is/state_machine.rb
|
14
|
+
lib/dm-is-state_machine/is/version.rb
|
15
|
+
spec/examples/invalid_events.rb
|
16
|
+
spec/examples/invalid_states.rb
|
17
|
+
spec/examples/invalid_transitions_1.rb
|
18
|
+
spec/examples/invalid_transitions_2.rb
|
19
|
+
spec/examples/traffic_light.rb
|
20
|
+
spec/integration/invalid_events_spec.rb
|
21
|
+
spec/integration/invalid_states_spec.rb
|
22
|
+
spec/integration/invalid_transitions_spec.rb
|
23
|
+
spec/integration/traffic_light_spec.rb
|
24
|
+
spec/spec.opts
|
25
|
+
spec/spec_helper.rb
|
26
|
+
spec/unit/data/event_spec.rb
|
27
|
+
spec/unit/data/machine_spec.rb
|
28
|
+
spec/unit/data/state_spec.rb
|
29
|
+
spec/unit/dsl/event_dsl_spec.rb
|
30
|
+
spec/unit/dsl/state_dsl_spec.rb
|
31
|
+
spec/unit/state_machine_spec.rb
|
data/README.txt
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
= dm-state-machine
|
2
|
+
|
3
|
+
DataMapper plugin that adds state machine functionality.
|
4
|
+
|
5
|
+
== Installation
|
6
|
+
|
7
|
+
Download dm-more and install dm-is-state_machine. Require it in your app.
|
8
|
+
|
9
|
+
== Getting started
|
10
|
+
|
11
|
+
Please refer to the integration specs in spec/integration, which refer to
|
12
|
+
spec/examples.
|
data/Rakefile
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'spec'
|
3
|
+
require 'spec/rake/spectask'
|
4
|
+
require 'pathname'
|
5
|
+
|
6
|
+
ROOT = Pathname(__FILE__).dirname.expand_path
|
7
|
+
require ROOT + 'lib/dm-is-state_machine/is/version'
|
8
|
+
|
9
|
+
AUTHOR = "David James"
|
10
|
+
EMAIL = "djwonk [a] collectiveinsight [d] net"
|
11
|
+
GEM_NAME = "dm-is-state_machine"
|
12
|
+
GEM_VERSION = DataMapper::Is::StateMachine::VERSION
|
13
|
+
GEM_DEPENDENCIES = [["dm-core", GEM_VERSION]]
|
14
|
+
GEM_CLEAN = ["log", "pkg"]
|
15
|
+
GEM_EXTRAS = { :has_rdoc => true, :extra_rdoc_files => %w[ README.txt LICENSE TODO ] }
|
16
|
+
|
17
|
+
PROJECT_NAME = "datamapper"
|
18
|
+
PROJECT_URL = "http://github.com/sam/dm-more/tree/master/dm-is-state_machine"
|
19
|
+
PROJECT_DESCRIPTION = PROJECT_SUMMARY = "DataMapper plugin for creating state machines"
|
20
|
+
|
21
|
+
require ROOT.parent + 'tasks/hoe'
|
22
|
+
|
23
|
+
task :default => [ :spec ]
|
24
|
+
|
25
|
+
WIN32 = (RUBY_PLATFORM =~ /win32|mingw|cygwin/) rescue nil
|
26
|
+
SUDO = WIN32 ? '' : ('sudo' unless ENV['SUDOLESS'])
|
27
|
+
|
28
|
+
desc "Install #{GEM_NAME} #{GEM_VERSION}"
|
29
|
+
task :install => [ :package ] do
|
30
|
+
sh "#{SUDO} gem install --local pkg/#{GEM_NAME}-#{GEM_VERSION} --no-update-sources", :verbose => false
|
31
|
+
end
|
32
|
+
|
33
|
+
desc "Uninstall #{GEM_NAME} #{GEM_VERSION} (default ruby)"
|
34
|
+
task :uninstall => [ :clobber ] do
|
35
|
+
sh "#{SUDO} gem uninstall #{GEM_NAME} -v#{GEM_VERSION} -I -x", :verbose => false
|
36
|
+
end
|
37
|
+
|
38
|
+
desc 'Run specifications'
|
39
|
+
Spec::Rake::SpecTask.new(:spec) do |t|
|
40
|
+
t.spec_opts << '--options' << 'spec/spec.opts' if File.exists?('spec/spec.opts')
|
41
|
+
t.spec_files = Pathname.glob(Pathname.new(__FILE__).dirname + 'spec/**/*_spec.rb')
|
42
|
+
|
43
|
+
begin
|
44
|
+
t.rcov = ENV.has_key?('NO_RCOV') ? ENV['NO_RCOV'] != 'true' : true
|
45
|
+
t.rcov_opts << '--exclude' << 'spec'
|
46
|
+
t.rcov_opts << '--text-summary'
|
47
|
+
t.rcov_opts << '--sort' << 'coverage' << '--sort-reverse'
|
48
|
+
rescue Exception
|
49
|
+
# rcov not installed
|
50
|
+
end
|
51
|
+
end
|
data/TODO
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
TODO
|
2
|
+
====
|
3
|
+
|
4
|
+
* Should skipping to a new state automatically trigger :enter Proc?
|
5
|
+
* Add loopback checking (i.e. when transitioning from a state back to itself)
|
6
|
+
* Add support for callbacks
|
7
|
+
* Consider using DataMapper's Enum type.
|
8
|
+
* Consider trying out a nested state machine.
|
9
|
+
* Not real happy with spec/unit/dsl:
|
10
|
+
- specs are brittle
|
11
|
+
- specs don't actually test much
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# Needed to import datamapper and other gems
|
2
|
+
require 'rubygems'
|
3
|
+
require 'pathname'
|
4
|
+
|
5
|
+
# Add all external dependencies for the plugin here
|
6
|
+
gem 'dm-core', '=0.9.4'
|
7
|
+
require 'dm-core'
|
8
|
+
|
9
|
+
# Require plugin-files
|
10
|
+
require Pathname(__FILE__).dirname.expand_path / 'dm-is-state_machine' / 'is' / 'state_machine'
|
11
|
+
require Pathname(__FILE__).dirname.expand_path / 'dm-is-state_machine' / 'is' / 'data' / 'event'
|
12
|
+
require Pathname(__FILE__).dirname.expand_path / 'dm-is-state_machine' / 'is' / 'data' / 'machine'
|
13
|
+
require Pathname(__FILE__).dirname.expand_path / 'dm-is-state_machine' / 'is' / 'data' / 'state'
|
14
|
+
require Pathname(__FILE__).dirname.expand_path / 'dm-is-state_machine' / 'is' / 'dsl' / 'event_dsl'
|
15
|
+
require Pathname(__FILE__).dirname.expand_path / 'dm-is-state_machine' / 'is' / 'dsl' / 'state_dsl'
|
16
|
+
|
17
|
+
# Include the plugin in Resource
|
18
|
+
module DataMapper
|
19
|
+
module Resource
|
20
|
+
module ClassMethods
|
21
|
+
include DataMapper::Is::StateMachine
|
22
|
+
end # module ClassMethods
|
23
|
+
end # module Resource
|
24
|
+
end # module DataMapper
|
25
|
+
|
26
|
+
# An alternative way to do the same thing as above:
|
27
|
+
# DataMapper::Model.append_extensions DataMapper::Is::StateMachine
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Is
|
3
|
+
module StateMachine
|
4
|
+
module Data
|
5
|
+
|
6
|
+
class Event
|
7
|
+
|
8
|
+
attr_reader :name, :machine, :transitions
|
9
|
+
|
10
|
+
def initialize(name, machine)
|
11
|
+
@name = name
|
12
|
+
@machine = machine
|
13
|
+
@transitions = []
|
14
|
+
end
|
15
|
+
|
16
|
+
def add_transition(from, to)
|
17
|
+
@transitions << { :from => from, :to => to }
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
end # Data
|
23
|
+
end # StateMachine
|
24
|
+
end # Is
|
25
|
+
end # DataMapper
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Is
|
3
|
+
module StateMachine
|
4
|
+
module Data
|
5
|
+
|
6
|
+
# Represents one state machine
|
7
|
+
class Machine
|
8
|
+
|
9
|
+
attr_reader :column, :initial
|
10
|
+
attr_accessor :current_state_name
|
11
|
+
attr_accessor :events, :states
|
12
|
+
|
13
|
+
def initialize(column, initial)
|
14
|
+
@column, @initial = column, initial
|
15
|
+
@events, @states = [], []
|
16
|
+
@current_state_name = initial
|
17
|
+
end
|
18
|
+
|
19
|
+
# Fire (activate) the event with name +event_name+
|
20
|
+
#
|
21
|
+
# @api public
|
22
|
+
def fire_event(event_name, resource)
|
23
|
+
unless event = find_event(event_name)
|
24
|
+
raise InvalidEvent, "Could not find event (#{event_name.inspect})"
|
25
|
+
end
|
26
|
+
transition = event.transitions.find do |t|
|
27
|
+
t[:from].to_s == @current_state_name.to_s
|
28
|
+
end
|
29
|
+
unless transition
|
30
|
+
raise InvalidEvent, "Event (#{event_name.inspect}) does " +
|
31
|
+
"not exist for current state (#{@current_state_name.inspect})"
|
32
|
+
end
|
33
|
+
@current_state_name = transition[:to]
|
34
|
+
|
35
|
+
# ===== Call :enter Proc if present =====
|
36
|
+
return unless enter_proc = current_state.options[:enter]
|
37
|
+
enter_proc.call(resource)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Return the current state
|
41
|
+
#
|
42
|
+
# @api public
|
43
|
+
def current_state
|
44
|
+
find_state(@current_state_name)
|
45
|
+
# TODO: add caching, i.e. with `@current_state ||= ...`
|
46
|
+
end
|
47
|
+
|
48
|
+
# Find event whose name is +event_name+
|
49
|
+
#
|
50
|
+
# @api semipublic
|
51
|
+
def find_event(event_name)
|
52
|
+
@events.find { |event| event.name.to_s == event_name.to_s }
|
53
|
+
# TODO: use a data structure that prevents duplicates
|
54
|
+
end
|
55
|
+
|
56
|
+
# Find state whose name is +event_name+
|
57
|
+
#
|
58
|
+
# @api semipublic
|
59
|
+
def find_state(state_name)
|
60
|
+
@states.find { |state| state.name.to_s == state_name.to_s }
|
61
|
+
# TODO: use a data structure that prevents duplicates
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
end # Data
|
67
|
+
end # StateMachine
|
68
|
+
end # Is
|
69
|
+
end # DataMapper
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Is
|
3
|
+
module StateMachine
|
4
|
+
module Data
|
5
|
+
|
6
|
+
class State
|
7
|
+
|
8
|
+
attr_reader :name, :machine, :options
|
9
|
+
|
10
|
+
def initialize(name, machine, options = {})
|
11
|
+
@name = name
|
12
|
+
@options = options
|
13
|
+
@machine = machine
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
end # Data
|
19
|
+
end # StateMachine
|
20
|
+
end # Is
|
21
|
+
end # DataMapper
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Is
|
3
|
+
module StateMachine
|
4
|
+
# Event DSL (Domain Specific Language)
|
5
|
+
module EventDsl
|
6
|
+
|
7
|
+
# Define an event. This takes a block which describes all valid
|
8
|
+
# transitions for this event.
|
9
|
+
#
|
10
|
+
# Example:
|
11
|
+
#
|
12
|
+
# class TrafficLight
|
13
|
+
# include DataMapper::Resource
|
14
|
+
# property :id, Serial
|
15
|
+
# is :state_machine, :initial => :green, :column => :color do
|
16
|
+
# # state definitions go here...
|
17
|
+
#
|
18
|
+
# event :forward do
|
19
|
+
# transition :from => :green, :to => :yellow
|
20
|
+
# transition :from => :yellow, :to => :red
|
21
|
+
# transition :from => :red, :to => :green
|
22
|
+
# end
|
23
|
+
# end
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# +transition+ takes a hash where <tt>:to</tt> is the state to transition
|
27
|
+
# to and <tt>:from</tt> is a state (or Array of states) from which this
|
28
|
+
# event can be fired.
|
29
|
+
def event(name, &block)
|
30
|
+
unless state_machine_context?(:is)
|
31
|
+
raise InvalidContext, "Valid only in 'is :state_machine' block"
|
32
|
+
end
|
33
|
+
|
34
|
+
# ===== Setup context =====
|
35
|
+
machine = @is_state_machine[:machine]
|
36
|
+
event = Data::Event.new(name, machine)
|
37
|
+
machine.events << event
|
38
|
+
@is_state_machine[:event] = {
|
39
|
+
:name => name,
|
40
|
+
:object => event
|
41
|
+
}
|
42
|
+
push_state_machine_context(:event)
|
43
|
+
|
44
|
+
# ===== Define methods =====
|
45
|
+
column = machine.column
|
46
|
+
define_method("#{name}!") do
|
47
|
+
machine.current_state_name = send(:"#{column}")
|
48
|
+
machine.fire_event(name, self)
|
49
|
+
send(:"#{column}=", machine.current_state_name)
|
50
|
+
end
|
51
|
+
|
52
|
+
yield if block_given?
|
53
|
+
|
54
|
+
# ===== Teardown context =====
|
55
|
+
pop_state_machine_context
|
56
|
+
end
|
57
|
+
|
58
|
+
def transition(options)
|
59
|
+
unless state_machine_context?(:event)
|
60
|
+
raise InvalidContext, "Valid only in 'event' block"
|
61
|
+
end
|
62
|
+
event_name = @is_state_machine[:event][:name]
|
63
|
+
event_object = @is_state_machine[:event][:object]
|
64
|
+
|
65
|
+
from = options[:from]
|
66
|
+
to = options[:to]
|
67
|
+
event_object.add_transition(from, to)
|
68
|
+
end
|
69
|
+
|
70
|
+
end # EventDsl
|
71
|
+
end # StateMachine
|
72
|
+
end # Is
|
73
|
+
end # DataMapper
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Is
|
3
|
+
module StateMachine
|
4
|
+
# State DSL (Domain Specific Language)
|
5
|
+
module StateDsl
|
6
|
+
|
7
|
+
# Define a state of the system.
|
8
|
+
#
|
9
|
+
# Example:
|
10
|
+
#
|
11
|
+
# class TrafficLight
|
12
|
+
# include DataMapper::Resource
|
13
|
+
# property :id, Serial
|
14
|
+
# is :state_machine do
|
15
|
+
# state :green, :enter => Proc.new { |o| o.log("G") }
|
16
|
+
# state :yellow, :enter => Proc.new { |o| o.log("Y") }
|
17
|
+
# state :red, :enter => Proc.new { |o| o.log("R") }
|
18
|
+
#
|
19
|
+
# # event definitions go here...
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# def log(string)
|
23
|
+
# Merb::Logger.info(string)
|
24
|
+
# end
|
25
|
+
# end
|
26
|
+
def state(name, options = {})
|
27
|
+
unless state_machine_context?(:is)
|
28
|
+
raise InvalidContext, "Valid only in 'is :state_machine' block"
|
29
|
+
end
|
30
|
+
|
31
|
+
# ===== Setup context =====
|
32
|
+
machine = @is_state_machine[:machine]
|
33
|
+
state = Data::State.new(name, machine, options)
|
34
|
+
machine.states << state
|
35
|
+
end
|
36
|
+
|
37
|
+
end # StateDsl
|
38
|
+
end # StateMachine
|
39
|
+
end # Is
|
40
|
+
end # DataMapper
|
@@ -0,0 +1,107 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Is
|
3
|
+
module StateMachine
|
4
|
+
|
5
|
+
class InvalidContext < RuntimeError; end
|
6
|
+
class InvalidState < RuntimeError; end
|
7
|
+
class InvalidEvent < RuntimeError; end
|
8
|
+
class EventConfusion < RuntimeError; end
|
9
|
+
class DuplicateStates < RuntimeError; end
|
10
|
+
class NoInitialState < RuntimeError; end
|
11
|
+
|
12
|
+
##
|
13
|
+
# Makes a column ('state' by default) act as a state machine. It will
|
14
|
+
# define the property if it does not exist.
|
15
|
+
#
|
16
|
+
# @example [Usage]
|
17
|
+
# is :state_machine
|
18
|
+
# is :state_machine, :initial => :internal
|
19
|
+
# is :state_machine, :column => :availability
|
20
|
+
# is :state_machine, :column => :availability, :initial => :external
|
21
|
+
#
|
22
|
+
# @param options<Hash> a hash of options
|
23
|
+
#
|
24
|
+
# @option :column<Symbol> the name of the custom column
|
25
|
+
#
|
26
|
+
def is_state_machine(options = {}, &block)
|
27
|
+
extend DataMapper::Is::StateMachine::EventDsl
|
28
|
+
extend DataMapper::Is::StateMachine::StateDsl
|
29
|
+
include DataMapper::Is::StateMachine::InstanceMethods
|
30
|
+
|
31
|
+
# ===== Setup context =====
|
32
|
+
options = { :column => :state, :initial => nil }.merge(options)
|
33
|
+
column = options[:column]
|
34
|
+
initial = options[:initial].to_s
|
35
|
+
unless properties.detect { |p| p.name == column }
|
36
|
+
property column, String, :default => initial
|
37
|
+
end
|
38
|
+
machine = Data::Machine.new(column, initial)
|
39
|
+
@is_state_machine = { :machine => machine }
|
40
|
+
|
41
|
+
# ===== Define callbacks =====
|
42
|
+
before :save do
|
43
|
+
if self.new_record?
|
44
|
+
# ...
|
45
|
+
else
|
46
|
+
# ...
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
before :destroy do
|
51
|
+
# Do we need to do anything here?
|
52
|
+
end
|
53
|
+
|
54
|
+
# ===== Setup context =====
|
55
|
+
push_state_machine_context(:is)
|
56
|
+
|
57
|
+
yield if block_given?
|
58
|
+
|
59
|
+
# ===== Teardown context =====
|
60
|
+
pop_state_machine_context
|
61
|
+
end
|
62
|
+
|
63
|
+
protected
|
64
|
+
|
65
|
+
def push_state_machine_context(label)
|
66
|
+
((@is_state_machine ||= {})[:context] ||= []) << label
|
67
|
+
|
68
|
+
# Less DRY, though more readable to some
|
69
|
+
# @is_state_machine ||= {}
|
70
|
+
# @is_state_machine[:context] ||= []
|
71
|
+
# @is_state_machine[:context] << label
|
72
|
+
end
|
73
|
+
|
74
|
+
def pop_state_machine_context
|
75
|
+
@is_state_machine[:context].pop
|
76
|
+
end
|
77
|
+
|
78
|
+
def state_machine_context?(label)
|
79
|
+
(i = @is_state_machine) && (c = i[:context]) &&
|
80
|
+
c.respond_to?(:include?) && c.include?(label)
|
81
|
+
end
|
82
|
+
|
83
|
+
module InstanceMethods
|
84
|
+
|
85
|
+
def initialize(*args)
|
86
|
+
super
|
87
|
+
# ===== Call :enter Proc if present =====
|
88
|
+
return unless is_sm = self.class.instance_variable_get(:@is_state_machine)
|
89
|
+
return unless machine = is_sm[:machine]
|
90
|
+
return unless initial = machine.initial
|
91
|
+
return unless initial_state = machine.find_state(initial)
|
92
|
+
return unless enter_proc = initial_state.options[:enter]
|
93
|
+
enter_proc.call(self)
|
94
|
+
end
|
95
|
+
|
96
|
+
end # InstanceMethods
|
97
|
+
|
98
|
+
end # StateMachine
|
99
|
+
end # Is
|
100
|
+
end # DataMapper
|
101
|
+
|
102
|
+
# Notes
|
103
|
+
# -----
|
104
|
+
#
|
105
|
+
# Since this gets mixed into a class, I try to keep the namespace pollution
|
106
|
+
# down to a minimum. This is why I only use the @is_state_machine instance
|
107
|
+
# variable.
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# An invalid example.
|
2
|
+
class InvalidEvents
|
3
|
+
include DataMapper::Resource
|
4
|
+
|
5
|
+
property :id, Serial
|
6
|
+
|
7
|
+
is :state_machine do
|
8
|
+
state :day
|
9
|
+
state :night
|
10
|
+
end
|
11
|
+
|
12
|
+
# The next lines are intentionally incorrect.
|
13
|
+
#
|
14
|
+
# 'event' only makes sense in a block under 'is :state_machine'
|
15
|
+
event :sunrise
|
16
|
+
event :sunset
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
InvalidEvents.auto_migrate!
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# An invalid example.
|
2
|
+
class InvalidStates
|
3
|
+
include DataMapper::Resource
|
4
|
+
|
5
|
+
property :id, Serial
|
6
|
+
|
7
|
+
is :state_machine do
|
8
|
+
event :sunrise
|
9
|
+
event :sunset
|
10
|
+
end
|
11
|
+
|
12
|
+
# The next lines are intentionally incorrect.
|
13
|
+
#
|
14
|
+
# 'state' only makes sense in a block under 'is :state_machine'
|
15
|
+
state :light
|
16
|
+
state :dark
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
InvalidStates.auto_migrate!
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# An invalid example.
|
2
|
+
class InvalidTransitions1
|
3
|
+
include DataMapper::Resource
|
4
|
+
|
5
|
+
property :id, Serial
|
6
|
+
|
7
|
+
is :state_machine do
|
8
|
+
state :happy
|
9
|
+
state :sad
|
10
|
+
|
11
|
+
event :toggle
|
12
|
+
|
13
|
+
# The next lines are intentionally incorrect.
|
14
|
+
#
|
15
|
+
# 'transition' is only valid when nested beneath 'event'
|
16
|
+
transition :to => :happy, :from => :sad
|
17
|
+
transition :to => :sad, :from => :happy
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
InvalidTransitions1.auto_migrate!
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# An invalid example.
|
2
|
+
class InvalidTransitions2
|
3
|
+
include DataMapper::Resource
|
4
|
+
|
5
|
+
property :id, Serial
|
6
|
+
|
7
|
+
is :state_machine do
|
8
|
+
state :happy
|
9
|
+
state :sad
|
10
|
+
|
11
|
+
event :toggle
|
12
|
+
end
|
13
|
+
|
14
|
+
# The next lines are intentionally incorrect.
|
15
|
+
#
|
16
|
+
# 'transition' is only valid when nested beneath 'event'
|
17
|
+
transition :to => :happy, :from => :sad
|
18
|
+
transition :to => :sad, :from => :happy
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
InvalidTransitions2.auto_migrate!
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# A valid example of a resource with a state machine.
|
2
|
+
class TrafficLight
|
3
|
+
include DataMapper::Resource
|
4
|
+
|
5
|
+
property :id, Serial # see note 1
|
6
|
+
|
7
|
+
is :state_machine, :initial => :green, :column => :color do
|
8
|
+
state :green, :enter => Proc.new { |o| o.log << "G" }
|
9
|
+
state :yellow, :enter => Proc.new { |o| o.log << "Y" }
|
10
|
+
state :red, :enter => Proc.new { |o| o.log << "R" }
|
11
|
+
|
12
|
+
event :forward do
|
13
|
+
transition :from => :green, :to => :yellow
|
14
|
+
transition :from => :yellow, :to => :red
|
15
|
+
transition :from => :red, :to => :green
|
16
|
+
end
|
17
|
+
|
18
|
+
event :backward do
|
19
|
+
transition :from => :green, :to => :red
|
20
|
+
transition :from => :yellow, :to => :green
|
21
|
+
transition :from => :red, :to => :yellow
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def log; @log ||= [] end
|
26
|
+
|
27
|
+
attr_reader :init
|
28
|
+
def initialize(*args)
|
29
|
+
(@init ||= []) << :init
|
30
|
+
super
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
TrafficLight.auto_migrate!
|
36
|
+
|
37
|
+
# ===== Note 1 =====
|
38
|
+
#
|
39
|
+
# One would expect that these two would be the same:
|
40
|
+
# property :id, Serial
|
41
|
+
# property :id, Integer, :serial => true
|
42
|
+
#
|
43
|
+
# But on 2008-07-05, the 2nd led to problems with an in-memory SQLite
|
44
|
+
# database.
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require Pathname(__FILE__).dirname.expand_path.parent + 'spec_helper'
|
3
|
+
|
4
|
+
describe "InvalidEvents" do
|
5
|
+
|
6
|
+
it "should get InvalidContext when requiring" do
|
7
|
+
lambda {
|
8
|
+
require File.join( File.dirname(__FILE__), "..", "examples", "invalid_events" )
|
9
|
+
}.should raise_error(DataMapper::Is::StateMachine::InvalidContext)
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require Pathname(__FILE__).dirname.expand_path.parent + 'spec_helper'
|
3
|
+
|
4
|
+
describe "InvalidStates" do
|
5
|
+
|
6
|
+
it "should get InvalidContext when requiring" do
|
7
|
+
lambda {
|
8
|
+
require File.join( File.dirname(__FILE__), "..", "examples", "invalid_states" )
|
9
|
+
}.should raise_error(DataMapper::Is::StateMachine::InvalidContext)
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require Pathname(__FILE__).dirname.expand_path.parent + 'spec_helper'
|
3
|
+
|
4
|
+
describe "InvalidTransitions1" do
|
5
|
+
|
6
|
+
it "should get InvalidContext when requiring" do
|
7
|
+
lambda {
|
8
|
+
require File.join( File.dirname(__FILE__), "..", "examples", "invalid_transitions_1" )
|
9
|
+
}.should raise_error(DataMapper::Is::StateMachine::InvalidContext)
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "InvalidTransitions2" do
|
15
|
+
|
16
|
+
it "should get InvalidContext when requiring" do
|
17
|
+
lambda {
|
18
|
+
require File.join( File.dirname(__FILE__), "..", "examples", "invalid_transitions_2" )
|
19
|
+
}.should raise_error(DataMapper::Is::StateMachine::InvalidContext)
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require Pathname(__FILE__).dirname.expand_path.parent + 'spec_helper'
|
3
|
+
require Pathname(__FILE__).dirname.expand_path.parent + 'examples/traffic_light'
|
4
|
+
|
5
|
+
describe TrafficLight do
|
6
|
+
|
7
|
+
before(:each) do
|
8
|
+
@t = TrafficLight.new
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should have an 'id' column" do
|
12
|
+
@t.attributes.should include(:id)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should have a 'color' column" do
|
16
|
+
@t.attributes.should include(:color)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should not have a 'state' column" do
|
20
|
+
@t.attributes.should_not include(:state)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should start off in the green state" do
|
24
|
+
@t.color.should == "green"
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should allow the color to be set" do
|
28
|
+
@t.color = :yellow
|
29
|
+
@t.save
|
30
|
+
@t.color.should == "yellow"
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should have called the :enter Proc" do
|
34
|
+
@t.log.should == %w(G)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should call the original initialize method" do
|
38
|
+
@t.init.should == [:init]
|
39
|
+
end
|
40
|
+
|
41
|
+
describe 'forward!' do
|
42
|
+
|
43
|
+
it "should respond to :forward!" do
|
44
|
+
@t.respond_to?(:forward!).should == true
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should transition to :yellow, :red, :green" do
|
48
|
+
@t.color.should == "green"
|
49
|
+
@t.forward!
|
50
|
+
@t.color.should == "yellow"
|
51
|
+
@t.log.should == %w(G Y)
|
52
|
+
@t.forward!
|
53
|
+
@t.color.should == "red"
|
54
|
+
@t.log.should == %w(G Y R)
|
55
|
+
@t.forward!
|
56
|
+
@t.color.should == "green"
|
57
|
+
@t.log.should == %w(G Y R G)
|
58
|
+
@t.new_record?.should == true
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should skip to :yellow then transition to :red, :green, :yellow" do
|
62
|
+
@t.color = :yellow
|
63
|
+
@t.color.should == "yellow"
|
64
|
+
@t.log.should == %w(G)
|
65
|
+
@t.forward!
|
66
|
+
@t.color.should == "red"
|
67
|
+
@t.log.should == %w(G R)
|
68
|
+
@t.forward!
|
69
|
+
@t.color.should == "green"
|
70
|
+
@t.log.should == %w(G R G)
|
71
|
+
@t.forward!
|
72
|
+
@t.color.should == "yellow"
|
73
|
+
@t.log.should == %w(G R G Y)
|
74
|
+
@t.new_record?.should == true
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should skip to :red then transition to :green, :yellow, :red" do
|
78
|
+
@t.color = :red
|
79
|
+
@t.color.should == "red"
|
80
|
+
@t.log.should == %w(G)
|
81
|
+
@t.forward!
|
82
|
+
@t.color.should == "green"
|
83
|
+
@t.log.should == %w(G G)
|
84
|
+
@t.forward!
|
85
|
+
@t.color.should == "yellow"
|
86
|
+
@t.log.should == %w(G G Y)
|
87
|
+
@t.forward!
|
88
|
+
@t.color.should == "red"
|
89
|
+
@t.log.should == %w(G G Y R)
|
90
|
+
@t.new_record?.should == true
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
describe 'backward!' do
|
96
|
+
|
97
|
+
it "should respond to 'backward!'" do
|
98
|
+
@t.respond_to?(:backward!).should == true
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should transition to :red, :yellow, :green" do
|
102
|
+
@t.color.should == "green"
|
103
|
+
@t.log.should == %w(G)
|
104
|
+
@t.backward!
|
105
|
+
@t.color.should == "red"
|
106
|
+
@t.log.should == %w(G R)
|
107
|
+
@t.backward!
|
108
|
+
@t.color.should == "yellow"
|
109
|
+
@t.log.should == %w(G R Y)
|
110
|
+
@t.backward!
|
111
|
+
@t.color.should == "green"
|
112
|
+
@t.log.should == %w(G R Y G)
|
113
|
+
@t.new_record?.should == true
|
114
|
+
end
|
115
|
+
|
116
|
+
it "should skip to :yellow then transition to :green, :red, :yellow" do
|
117
|
+
@t.color = :yellow
|
118
|
+
@t.color.should == "yellow"
|
119
|
+
@t.log.should == %w(G)
|
120
|
+
@t.backward!
|
121
|
+
@t.color.should == "green"
|
122
|
+
@t.log.should == %w(G G)
|
123
|
+
@t.backward!
|
124
|
+
@t.color.should == "red"
|
125
|
+
@t.log.should == %w(G G R)
|
126
|
+
@t.backward!
|
127
|
+
@t.color.should == "yellow"
|
128
|
+
@t.log.should == %w(G G R Y)
|
129
|
+
@t.new_record?.should == true
|
130
|
+
end
|
131
|
+
|
132
|
+
it "should skip to :red then transition to :yellow, :green, :red" do
|
133
|
+
@t.color = :red
|
134
|
+
@t.color.should == "red"
|
135
|
+
@t.log.should == %w(G)
|
136
|
+
@t.backward!
|
137
|
+
@t.color.should == "yellow"
|
138
|
+
@t.log.should == %w(G Y)
|
139
|
+
@t.backward!
|
140
|
+
@t.color.should == "green"
|
141
|
+
@t.log.should == %w(G Y G)
|
142
|
+
@t.backward!
|
143
|
+
@t.color.should == "red"
|
144
|
+
@t.log.should == %w(G Y G R)
|
145
|
+
@t.new_record?.should == true
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
data/spec/spec.opts
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
gem 'rspec', '>=1.1.3'
|
3
|
+
require 'spec'
|
4
|
+
require 'pathname'
|
5
|
+
require Pathname(__FILE__).dirname.expand_path.parent + 'lib/dm-is-state_machine'
|
6
|
+
|
7
|
+
def load_driver(name, default_uri)
|
8
|
+
return false if ENV['ADAPTER'] != name.to_s
|
9
|
+
|
10
|
+
lib = "do_#{name}"
|
11
|
+
|
12
|
+
begin
|
13
|
+
gem lib, '=0.9.4'
|
14
|
+
require lib
|
15
|
+
DataMapper.setup(name, ENV["#{name.to_s.upcase}_SPEC_URI"] || default_uri)
|
16
|
+
DataMapper::Repository.adapters[:default] = DataMapper::Repository.adapters[name]
|
17
|
+
true
|
18
|
+
rescue Gem::LoadError => e
|
19
|
+
warn "Could not load #{lib}: #{e}"
|
20
|
+
false
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
ENV['ADAPTER'] ||= 'sqlite3'
|
25
|
+
|
26
|
+
HAS_SQLITE3 = load_driver(:sqlite3, 'sqlite3::memory:')
|
27
|
+
HAS_MYSQL = load_driver(:mysql, 'mysql://localhost/dm_core_test')
|
28
|
+
HAS_POSTGRES = load_driver(:postgres, 'postgres://postgres@localhost/dm_core_test')
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require Pathname(__FILE__).dirname.expand_path.parent.parent + 'spec_helper'
|
3
|
+
|
4
|
+
module EventHelper
|
5
|
+
def new_event(*args)
|
6
|
+
DataMapper::Is::StateMachine::Data::Event.new(*args)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
describe DataMapper::Is::StateMachine::Data::Event do
|
11
|
+
include EventHelper
|
12
|
+
|
13
|
+
before(:each) do
|
14
|
+
@machine = mock("machine")
|
15
|
+
@event = new_event(:ping, @machine)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "#initialize should work" do
|
19
|
+
@event.name.should == :ping
|
20
|
+
@event.machine.should == @machine
|
21
|
+
@event.transitions.should == []
|
22
|
+
end
|
23
|
+
|
24
|
+
it "#add_transition should work" do
|
25
|
+
@event.add_transition(:nothing, :pinged)
|
26
|
+
@event.transitions.should == [{:from => :nothing, :to => :pinged }]
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require Pathname(__FILE__).dirname.expand_path.parent.parent + 'spec_helper'
|
3
|
+
|
4
|
+
module MachineHelper
|
5
|
+
def new_machine(*args)
|
6
|
+
DataMapper::Is::StateMachine::Data::Machine.new(*args)
|
7
|
+
end
|
8
|
+
|
9
|
+
def new_state(name, machine, options = {})
|
10
|
+
mock(name, :name => name, :machine => machine, :options => options)
|
11
|
+
end
|
12
|
+
|
13
|
+
def new_event(name, machine)
|
14
|
+
mock(name, :name => name, :machine => machine)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe DataMapper::Is::StateMachine::Data::Machine do
|
19
|
+
include MachineHelper
|
20
|
+
|
21
|
+
describe "new Machine, no events" do
|
22
|
+
before(:each) do
|
23
|
+
@machine = new_machine(:power, :off)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "#column should work" do
|
27
|
+
@machine.column.should == :power
|
28
|
+
end
|
29
|
+
|
30
|
+
it "#initial should work" do
|
31
|
+
@machine.initial.should == :off
|
32
|
+
end
|
33
|
+
|
34
|
+
it "#events should work" do
|
35
|
+
@machine.events.should == []
|
36
|
+
end
|
37
|
+
|
38
|
+
it "#states should work" do
|
39
|
+
@machine.states.should == []
|
40
|
+
end
|
41
|
+
|
42
|
+
it "#find_event should return nothing" do
|
43
|
+
@machine.find_event(:turn_on).should == nil
|
44
|
+
end
|
45
|
+
|
46
|
+
it "#fire_event should raise error" do
|
47
|
+
lambda {
|
48
|
+
@machine.fire_event(:turn_on, nil)
|
49
|
+
}.should raise_error(DataMapper::Is::StateMachine::InvalidEvent)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe "new Machine, 2 states, 1 event" do
|
54
|
+
before(:each) do
|
55
|
+
@machine = new_machine(:power, :off)
|
56
|
+
@machine.states << (@off_state = new_state(:off, @machine))
|
57
|
+
@machine.states << (@on_state = new_state(:on, @machine))
|
58
|
+
@machine.events << (@turn_on = new_event(:turn_on, @machine))
|
59
|
+
@turn_on.stub!(:transitions).and_return([{ :from => :off, :to => :on }])
|
60
|
+
end
|
61
|
+
|
62
|
+
it "#column should work" do
|
63
|
+
@machine.column.should == :power
|
64
|
+
end
|
65
|
+
|
66
|
+
it "#initial should work" do
|
67
|
+
@machine.initial.should == :off
|
68
|
+
end
|
69
|
+
|
70
|
+
it "#events should work" do
|
71
|
+
@machine.events.should == [@turn_on]
|
72
|
+
end
|
73
|
+
|
74
|
+
it "#states should work" do
|
75
|
+
@machine.states.should == [@off_state, @on_state]
|
76
|
+
end
|
77
|
+
|
78
|
+
it "#current_state should work" do
|
79
|
+
@machine.current_state.should == @off_state
|
80
|
+
end
|
81
|
+
|
82
|
+
it "#current_state_name should work" do
|
83
|
+
@machine.current_state_name.should == :off
|
84
|
+
end
|
85
|
+
|
86
|
+
it "#find_event should return nothing" do
|
87
|
+
@machine.find_event(:turn_on).should == @turn_on
|
88
|
+
end
|
89
|
+
|
90
|
+
it "#fire_event should work" do
|
91
|
+
@machine.fire_event(:turn_on, nil)
|
92
|
+
@machine.current_state.should == @on_state
|
93
|
+
@machine.current_state_name.should == :on
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require Pathname(__FILE__).dirname.expand_path.parent.parent + 'spec_helper'
|
3
|
+
|
4
|
+
module StateHelper
|
5
|
+
def new_state(*args)
|
6
|
+
DataMapper::Is::StateMachine::Data::State.new(*args)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
describe DataMapper::Is::StateMachine::Data::State do
|
11
|
+
include StateHelper
|
12
|
+
|
13
|
+
before(:each) do
|
14
|
+
@machine = mock("machine")
|
15
|
+
@state = new_state(:off, @machine)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "#initialize should work" do
|
19
|
+
@state.name.should == :off
|
20
|
+
@state.machine.should == @machine
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require Pathname(__FILE__).dirname.expand_path.parent.parent + 'spec_helper'
|
3
|
+
|
4
|
+
describe "EventDsl" do
|
5
|
+
|
6
|
+
describe "event" do
|
7
|
+
|
8
|
+
before(:each) do
|
9
|
+
class Earth
|
10
|
+
extend DataMapper::Is::StateMachine::EventDsl
|
11
|
+
stub!(:state_machine_context?).and_return(true)
|
12
|
+
stub!(:push_state_machine_context)
|
13
|
+
stub!(:pop_state_machine_context)
|
14
|
+
end
|
15
|
+
machine = mock("machine", :events => [], :column => :state)
|
16
|
+
Earth.instance_variable_set(:@is_state_machine, { :machine => machine })
|
17
|
+
end
|
18
|
+
|
19
|
+
it "declaration should succeed" do
|
20
|
+
class Earth
|
21
|
+
event :sunrise
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "transition" do
|
28
|
+
|
29
|
+
before(:each) do
|
30
|
+
|
31
|
+
class Earth
|
32
|
+
extend DataMapper::Is::StateMachine::EventDsl
|
33
|
+
|
34
|
+
stub!(:state_machine_context?).and_return(true)
|
35
|
+
stub!(:push_state_machine_context)
|
36
|
+
stub!(:pop_state_machine_context)
|
37
|
+
end
|
38
|
+
|
39
|
+
machine = mock("machine", :events => [], :column => :state)
|
40
|
+
event = mock("sunrise_event")
|
41
|
+
event.stub!(:add_transition)
|
42
|
+
Earth.instance_variable_set(:@is_state_machine, {
|
43
|
+
:machine => machine,
|
44
|
+
:event => { :name => :sunrise, :object => event }
|
45
|
+
})
|
46
|
+
end
|
47
|
+
|
48
|
+
it "transition definition should succeed" do
|
49
|
+
class Earth
|
50
|
+
transition :from => :night, :to => :day
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require Pathname(__FILE__).dirname.expand_path.parent.parent + 'spec_helper'
|
3
|
+
|
4
|
+
describe "StateDsl" do
|
5
|
+
|
6
|
+
describe "state" do
|
7
|
+
|
8
|
+
before(:each) do
|
9
|
+
class Earth
|
10
|
+
extend DataMapper::Is::StateMachine::StateDsl
|
11
|
+
stub!(:state_machine_context?).and_return(true)
|
12
|
+
end
|
13
|
+
machine = mock("machine", :states => [])
|
14
|
+
Earth.instance_variable_set(:@is_state_machine, { :machine => machine })
|
15
|
+
end
|
16
|
+
|
17
|
+
it "declaration should succeed" do
|
18
|
+
class Earth
|
19
|
+
state :day
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require Pathname(__FILE__).dirname.expand_path.parent + 'spec_helper'
|
3
|
+
|
4
|
+
describe "StateMachine" do
|
5
|
+
|
6
|
+
describe "is_state_machine" do
|
7
|
+
|
8
|
+
before(:each) do
|
9
|
+
class Earth
|
10
|
+
extend DataMapper::Is::StateMachine
|
11
|
+
|
12
|
+
stub!(:properties).and_return([])
|
13
|
+
stub!(:property)
|
14
|
+
stub!(:before)
|
15
|
+
|
16
|
+
stub!(:state_machine_context?).and_return(true)
|
17
|
+
stub!(:push_state_machine_context)
|
18
|
+
stub!(:pop_state_machine_context)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
it "declaration should succeed" do
|
23
|
+
class Earth
|
24
|
+
is_state_machine
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# is_state_machine
|
32
|
+
# push_state_machine_context(label)
|
33
|
+
# pop_state_machine_context
|
34
|
+
# state_machine_context?(label)
|
metadata
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dm-is-state_machine
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.9.4
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- David James
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-08-21 00:00:00 -05:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: dm-core
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - "="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.9.4
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: hoe
|
27
|
+
type: :development
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.7.0
|
34
|
+
version:
|
35
|
+
description: DataMapper plugin for creating state machines
|
36
|
+
email:
|
37
|
+
- djwonk [a] collectiveinsight [d] net
|
38
|
+
executables: []
|
39
|
+
|
40
|
+
extensions: []
|
41
|
+
|
42
|
+
extra_rdoc_files:
|
43
|
+
- README.txt
|
44
|
+
- LICENSE
|
45
|
+
- TODO
|
46
|
+
files:
|
47
|
+
- History.txt
|
48
|
+
- LICENSE
|
49
|
+
- Manifest.txt
|
50
|
+
- README.txt
|
51
|
+
- Rakefile
|
52
|
+
- TODO
|
53
|
+
- lib/dm-is-state_machine.rb
|
54
|
+
- lib/dm-is-state_machine/is/data/event.rb
|
55
|
+
- lib/dm-is-state_machine/is/data/machine.rb
|
56
|
+
- lib/dm-is-state_machine/is/data/state.rb
|
57
|
+
- lib/dm-is-state_machine/is/dsl/event_dsl.rb
|
58
|
+
- lib/dm-is-state_machine/is/dsl/state_dsl.rb
|
59
|
+
- lib/dm-is-state_machine/is/state_machine.rb
|
60
|
+
- lib/dm-is-state_machine/is/version.rb
|
61
|
+
- spec/examples/invalid_events.rb
|
62
|
+
- spec/examples/invalid_states.rb
|
63
|
+
- spec/examples/invalid_transitions_1.rb
|
64
|
+
- spec/examples/invalid_transitions_2.rb
|
65
|
+
- spec/examples/traffic_light.rb
|
66
|
+
- spec/integration/invalid_events_spec.rb
|
67
|
+
- spec/integration/invalid_states_spec.rb
|
68
|
+
- spec/integration/invalid_transitions_spec.rb
|
69
|
+
- spec/integration/traffic_light_spec.rb
|
70
|
+
- spec/spec.opts
|
71
|
+
- spec/spec_helper.rb
|
72
|
+
- spec/unit/data/event_spec.rb
|
73
|
+
- spec/unit/data/machine_spec.rb
|
74
|
+
- spec/unit/data/state_spec.rb
|
75
|
+
- spec/unit/dsl/event_dsl_spec.rb
|
76
|
+
- spec/unit/dsl/state_dsl_spec.rb
|
77
|
+
- spec/unit/state_machine_spec.rb
|
78
|
+
has_rdoc: true
|
79
|
+
homepage: http://github.com/sam/dm-more/tree/master/dm-is-state_machine
|
80
|
+
post_install_message:
|
81
|
+
rdoc_options:
|
82
|
+
- --main
|
83
|
+
- README.txt
|
84
|
+
require_paths:
|
85
|
+
- lib
|
86
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: "0"
|
91
|
+
version:
|
92
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: "0"
|
97
|
+
version:
|
98
|
+
requirements: []
|
99
|
+
|
100
|
+
rubyforge_project: datamapper
|
101
|
+
rubygems_version: 1.2.0
|
102
|
+
signing_key:
|
103
|
+
specification_version: 2
|
104
|
+
summary: DataMapper plugin for creating state machines
|
105
|
+
test_files: []
|
106
|
+
|