halorgium-dm-is-state_machine 0.10.2.via
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.rdoc +102 -0
- data/lib/dm-is-state_machine.rb +8 -0
- data/lib/dm-is-state_machine/is/data/event.rb +25 -0
- data/lib/dm-is-state_machine/is/data/machine.rb +142 -0
- data/lib/dm-is-state_machine/is/data/state.rb +21 -0
- data/lib/dm-is-state_machine/is/dsl/event_dsl.rb +110 -0
- data/lib/dm-is-state_machine/is/dsl/state_dsl.rb +40 -0
- data/lib/dm-is-state_machine/is/state_machine.rb +126 -0
- metadata +124 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 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/README.rdoc
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
= dm-is-state_machine
|
2
|
+
|
3
|
+
DataMapper plugin that adds state machine functionality to your models.
|
4
|
+
|
5
|
+
== Why is this plugin useful?
|
6
|
+
|
7
|
+
Your DataMapper resource might benefit from a state machine if it:
|
8
|
+
|
9
|
+
* has different "modes" of operation
|
10
|
+
* has discrete behaviors
|
11
|
+
* especially if the behaviors are mutually exclusive
|
12
|
+
|
13
|
+
And you want a clean, high-level way of describing these modes / behaviors
|
14
|
+
and how the resource moves between them. This plugin allows you to
|
15
|
+
declaratively describe the states and transitions involved.
|
16
|
+
|
17
|
+
== Installation
|
18
|
+
|
19
|
+
1. Download dm-more.
|
20
|
+
2. Install dm-is-state_machine using the supplied rake files.
|
21
|
+
|
22
|
+
== Setting up with Merb ##
|
23
|
+
|
24
|
+
Add this line to your init.rb:
|
25
|
+
|
26
|
+
dependency "dm-is-state_machine"
|
27
|
+
|
28
|
+
## Example DataMapper resource (i.e. model) ##
|
29
|
+
|
30
|
+
# /app/models/traffic_light.rb
|
31
|
+
class TrafficLight
|
32
|
+
include DataMapper::Resource
|
33
|
+
|
34
|
+
property :id, Serial
|
35
|
+
|
36
|
+
is :state_machine, :initial => :green, :column => :color do
|
37
|
+
state :green
|
38
|
+
state :yellow
|
39
|
+
state :red, :enter => :red_hook
|
40
|
+
state :broken
|
41
|
+
|
42
|
+
event :forward do
|
43
|
+
transition :from => :green, :to => :yellow
|
44
|
+
transition :from => :yellow, :to => :red
|
45
|
+
transition :from => :red, :to => :green
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def red_hook
|
50
|
+
# Do something
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
== What this gives you
|
55
|
+
|
56
|
+
=== Explained in words
|
57
|
+
|
58
|
+
The above DSL (domain specific language) does these things "behind the scenes":
|
59
|
+
|
60
|
+
1. Defines a DataMapper property called 'color'.
|
61
|
+
|
62
|
+
2. Makes the current state available by using 'traffic_light.color'.
|
63
|
+
|
64
|
+
3. Defines the 'forward!' transition method. This method triggers the
|
65
|
+
appropriate transition based on the current state and comparing it against
|
66
|
+
the various :from states. It will raise an error if you attempt to call
|
67
|
+
it with an invalid state (such as :broken, see above). After the method
|
68
|
+
runs successfully, the state machine will be left in the :to state.
|
69
|
+
|
70
|
+
=== Explained with some code examples
|
71
|
+
|
72
|
+
# Somewhere in your controller, perhaps
|
73
|
+
light = TrafficLight.new
|
74
|
+
|
75
|
+
# Move to the next state
|
76
|
+
light.forward!
|
77
|
+
|
78
|
+
# Do something based on the current state
|
79
|
+
case light.color
|
80
|
+
when "green"
|
81
|
+
# do something green-related
|
82
|
+
when "yellow"
|
83
|
+
# do something yellow-related
|
84
|
+
when "red"
|
85
|
+
# do something red-related
|
86
|
+
end
|
87
|
+
|
88
|
+
== Specific examples
|
89
|
+
|
90
|
+
We would also like to hear how *you* are using state machines in your code.
|
91
|
+
|
92
|
+
== See also
|
93
|
+
|
94
|
+
Here are some other projects you might want to look at. Most of them
|
95
|
+
are probably intended for ActiveRecord. They take different approaches,
|
96
|
+
which is pretty interesting. If you find something you like in these other
|
97
|
+
projects, let us know. Maybe we can incorporate some of your favorite parts.
|
98
|
+
That said, I do not want to create a Frankenstein. :)
|
99
|
+
|
100
|
+
* http://github.com/pluginaweek/state_machine/tree/master
|
101
|
+
* http://github.com/davidlee/stateful/tree/master
|
102
|
+
* http://github.com/sbfaulkner/has_states/tree/master
|
@@ -0,0 +1,8 @@
|
|
1
|
+
require 'dm-is-state_machine/is/state_machine'
|
2
|
+
require 'dm-is-state_machine/is/data/event'
|
3
|
+
require 'dm-is-state_machine/is/data/machine'
|
4
|
+
require 'dm-is-state_machine/is/data/state'
|
5
|
+
require 'dm-is-state_machine/is/dsl/event_dsl'
|
6
|
+
require 'dm-is-state_machine/is/dsl/state_dsl'
|
7
|
+
|
8
|
+
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, via)
|
17
|
+
@transitions << { :from => from, :to => to, :via => via }
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
end # Data
|
23
|
+
end # StateMachine
|
24
|
+
end # Is
|
25
|
+
end # DataMapper
|
@@ -0,0 +1,142 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Is
|
3
|
+
module StateMachine
|
4
|
+
module Data
|
5
|
+
|
6
|
+
class Machine
|
7
|
+
def initialize(definition, resource)
|
8
|
+
@definition = definition
|
9
|
+
@resource = resource
|
10
|
+
end
|
11
|
+
|
12
|
+
def run_initial
|
13
|
+
return unless initial
|
14
|
+
return unless initial_state = @definition.find_state(initial)
|
15
|
+
run_hook_if_present initial_state.options[:enter]
|
16
|
+
end
|
17
|
+
|
18
|
+
# hook may be either a Proc or symbol
|
19
|
+
def run_hook_if_present(hook)
|
20
|
+
return unless hook
|
21
|
+
if hook.respond_to?(:call)
|
22
|
+
hook.call(@resource)
|
23
|
+
else
|
24
|
+
@resource.__send__(hook)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def initial
|
29
|
+
@definition.initial
|
30
|
+
end
|
31
|
+
|
32
|
+
def find_event(event_name)
|
33
|
+
@definition.find_event(event_name)
|
34
|
+
end
|
35
|
+
|
36
|
+
def state?(state_name)
|
37
|
+
if @definition.find_state(state_name)
|
38
|
+
current_state_name == state_name.to_s
|
39
|
+
else
|
40
|
+
raise InvalidState.new("Invalid state: #{state_name.inspect}")
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def fire_event(event_name)
|
45
|
+
transition = @definition.fire_event(event_name, current_state_name)
|
46
|
+
|
47
|
+
if via_state_name = transition[:via]
|
48
|
+
self.current_state_name = via_state_name
|
49
|
+
end
|
50
|
+
|
51
|
+
# == Change the current_state ==
|
52
|
+
self.current_state_name = transition[:to]
|
53
|
+
end
|
54
|
+
|
55
|
+
# Return the current state
|
56
|
+
#
|
57
|
+
# @api public
|
58
|
+
def current_state
|
59
|
+
@definition.find_state(current_state_name)
|
60
|
+
# TODO: add caching, i.e. with `@current_state ||= ...`
|
61
|
+
end
|
62
|
+
|
63
|
+
def current_state_name
|
64
|
+
@resource.attribute_get(@definition.column).to_s
|
65
|
+
end
|
66
|
+
|
67
|
+
def current_state_name=(state_name)
|
68
|
+
# == Run :exit hook (if present) ==
|
69
|
+
run_hook_if_present current_state.options[:exit]
|
70
|
+
|
71
|
+
@resource.update(@definition.column => state_name.to_s)
|
72
|
+
|
73
|
+
# == Run :enter hook (if present) ==
|
74
|
+
run_hook_if_present current_state.options[:enter]
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# This Machine class represents one state machine.
|
79
|
+
#
|
80
|
+
# A model (i.e. a DataMapper resource) can have more than one Machine.
|
81
|
+
class MachineDefinition
|
82
|
+
|
83
|
+
# The property of the DM resource that will hold this Machine's
|
84
|
+
# state.
|
85
|
+
#
|
86
|
+
# TODO: change :column to :property
|
87
|
+
attr_accessor :column
|
88
|
+
|
89
|
+
# The initial value of this Machine's state
|
90
|
+
attr_accessor :initial
|
91
|
+
|
92
|
+
attr_accessor :events
|
93
|
+
|
94
|
+
attr_accessor :states
|
95
|
+
|
96
|
+
def initialize(column, initial)
|
97
|
+
@column, @initial = column, initial
|
98
|
+
@events, @states = [], []
|
99
|
+
end
|
100
|
+
|
101
|
+
# Fire (activate) the event with name +event_name+
|
102
|
+
#
|
103
|
+
# @api public
|
104
|
+
def fire_event(event_name, current_state_name)
|
105
|
+
event_name = event_name.to_s
|
106
|
+
unless event = find_event(event_name)
|
107
|
+
raise InvalidEvent, "Could not find event (#{event_name.inspect})"
|
108
|
+
end
|
109
|
+
transition = event.transitions.find do |t|
|
110
|
+
Array(t[:from]).any? do |from_state|
|
111
|
+
from_state.to_s == current_state_name
|
112
|
+
end
|
113
|
+
end
|
114
|
+
unless transition
|
115
|
+
raise InvalidEvent, "Event (#{event_name.inspect}) does not " +
|
116
|
+
"exist for current state (#{current_state_name.inspect})"
|
117
|
+
end
|
118
|
+
transition
|
119
|
+
end
|
120
|
+
|
121
|
+
# Find event whose name is +event_name+
|
122
|
+
#
|
123
|
+
# @api semipublic
|
124
|
+
def find_event(event_name)
|
125
|
+
@events.find { |event| event.name.to_s == event_name.to_s }
|
126
|
+
# TODO: use a data structure that prevents duplicates
|
127
|
+
end
|
128
|
+
|
129
|
+
# Find state whose name is +event_name+
|
130
|
+
#
|
131
|
+
# @api semipublic
|
132
|
+
def find_state(state_name)
|
133
|
+
@states.find { |state| state.name.to_s == state_name.to_s }
|
134
|
+
# TODO: use a data structure that prevents duplicates
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
138
|
+
|
139
|
+
end # Data
|
140
|
+
end # StateMachine
|
141
|
+
end # Is
|
142
|
+
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,110 @@
|
|
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
|
+
if method_defined?("#{name}!")
|
35
|
+
raise InvalidEvent, "There is a method called #{name}! on #{self}"
|
36
|
+
end
|
37
|
+
|
38
|
+
event_object = create_event(name)
|
39
|
+
|
40
|
+
# ===== Setup context =====
|
41
|
+
@is_state_machine[:event] = {
|
42
|
+
:name => name,
|
43
|
+
:object => event_object
|
44
|
+
}
|
45
|
+
push_state_machine_context(:event)
|
46
|
+
|
47
|
+
# ===== Define methods =====
|
48
|
+
define_method("#{name}!") do
|
49
|
+
transition!(name)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Possible alternative to the above:
|
53
|
+
# (class_eval is typically faster than define_method)
|
54
|
+
#
|
55
|
+
# self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
56
|
+
# def #{name}!
|
57
|
+
# machine.current_state_name = __send__(:"#{column}")
|
58
|
+
# machine.fire_event(name, self)
|
59
|
+
# __send__(:"#{column}="), machine.current_state_name
|
60
|
+
# end
|
61
|
+
# RUBY
|
62
|
+
|
63
|
+
yield if block_given?
|
64
|
+
|
65
|
+
# ===== Teardown context =====
|
66
|
+
pop_state_machine_context
|
67
|
+
end
|
68
|
+
|
69
|
+
def destroy(options)
|
70
|
+
unless state_machine_context?(:is)
|
71
|
+
raise InvalidContext, "Valid only in 'is :state_machine' block"
|
72
|
+
end
|
73
|
+
|
74
|
+
event_object = create_event(:destroy)
|
75
|
+
from = options[:from]
|
76
|
+
to = options[:to]
|
77
|
+
via = options[:via]
|
78
|
+
event_object.add_transition(from, to, via)
|
79
|
+
end
|
80
|
+
|
81
|
+
def create_event(name)
|
82
|
+
unless state_machine_context?(:is)
|
83
|
+
raise InvalidContext, "Valid only in 'is :state_machine' block"
|
84
|
+
end
|
85
|
+
|
86
|
+
name = name.to_s
|
87
|
+
|
88
|
+
definition = @is_state_machine[:definition]
|
89
|
+
event = Data::Event.new(name, definition)
|
90
|
+
definition.events << event
|
91
|
+
event
|
92
|
+
end
|
93
|
+
|
94
|
+
def transition(options)
|
95
|
+
unless state_machine_context?(:event)
|
96
|
+
raise InvalidContext, "Valid only in 'event' block"
|
97
|
+
end
|
98
|
+
event_name = @is_state_machine[:event][:name]
|
99
|
+
event_object = @is_state_machine[:event][:object]
|
100
|
+
|
101
|
+
from = options[:from]
|
102
|
+
to = options[:to]
|
103
|
+
via = options[:via]
|
104
|
+
event_object.add_transition(from, to, via)
|
105
|
+
end
|
106
|
+
|
107
|
+
end # EventDsl
|
108
|
+
end # StateMachine
|
109
|
+
end # Is
|
110
|
+
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
|
+
definition = @is_state_machine[:definition]
|
33
|
+
state = Data::State.new(name, definition, options)
|
34
|
+
definition.states << state
|
35
|
+
end
|
36
|
+
|
37
|
+
end # StateDsl
|
38
|
+
end # StateMachine
|
39
|
+
end # Is
|
40
|
+
end # DataMapper
|
@@ -0,0 +1,126 @@
|
|
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
|
+
definition = Data::MachineDefinition.new(column, initial)
|
39
|
+
@is_state_machine = { :definition => definition }
|
40
|
+
|
41
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
42
|
+
def #{column}=(value)
|
43
|
+
value = value.to_s if value.kind_of?(Symbol)
|
44
|
+
attribute_set(#{column.inspect}, value)
|
45
|
+
end
|
46
|
+
RUBY
|
47
|
+
|
48
|
+
# ===== Define callbacks =====
|
49
|
+
# TODO: define callbacks
|
50
|
+
# before :save do
|
51
|
+
# if self.new_record?
|
52
|
+
# # ...
|
53
|
+
# else
|
54
|
+
# # ...
|
55
|
+
# end
|
56
|
+
# end
|
57
|
+
|
58
|
+
before :destroy do
|
59
|
+
if state_machine.find_event(:destroy)
|
60
|
+
transition!(:destroy)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# ===== Setup context =====
|
65
|
+
push_state_machine_context(:is)
|
66
|
+
|
67
|
+
yield if block_given?
|
68
|
+
|
69
|
+
# ===== Teardown context =====
|
70
|
+
pop_state_machine_context
|
71
|
+
end
|
72
|
+
|
73
|
+
protected
|
74
|
+
|
75
|
+
def push_state_machine_context(label)
|
76
|
+
@is_state_machine ||= {}
|
77
|
+
@is_state_machine[:context] ||= []
|
78
|
+
@is_state_machine[:context] << label
|
79
|
+
|
80
|
+
# Compacted, but barely readable for humans
|
81
|
+
# ((@is_state_machine ||= {})[:context] ||= []) << label
|
82
|
+
end
|
83
|
+
|
84
|
+
def pop_state_machine_context
|
85
|
+
@is_state_machine[:context].pop
|
86
|
+
end
|
87
|
+
|
88
|
+
def state_machine_context?(label)
|
89
|
+
(i = @is_state_machine) && (c = i[:context]) &&
|
90
|
+
c.respond_to?(:include?) && c.include?(label)
|
91
|
+
end
|
92
|
+
|
93
|
+
module InstanceMethods
|
94
|
+
|
95
|
+
def initialize(*args)
|
96
|
+
super
|
97
|
+
# ===== Run :enter hook if present =====
|
98
|
+
state_machine.run_initial
|
99
|
+
end
|
100
|
+
|
101
|
+
def transition!(event_name)
|
102
|
+
state_machine.fire_event(event_name)
|
103
|
+
end
|
104
|
+
|
105
|
+
def state?(state_name)
|
106
|
+
state_machine.state?(state_name)
|
107
|
+
end
|
108
|
+
|
109
|
+
def state_machine
|
110
|
+
return unless is_sm = model.instance_variable_get(:@is_state_machine)
|
111
|
+
return unless definition = is_sm[:definition]
|
112
|
+
Data::Machine.new(definition, self)
|
113
|
+
end
|
114
|
+
|
115
|
+
end # InstanceMethods
|
116
|
+
|
117
|
+
end # StateMachine
|
118
|
+
end # Is
|
119
|
+
end # DataMapper
|
120
|
+
|
121
|
+
# Notes
|
122
|
+
# -----
|
123
|
+
#
|
124
|
+
# Since this gets mixed into a class, I try to keep the namespace pollution
|
125
|
+
# down to a minimum. This is why I only use the @is_state_machine instance
|
126
|
+
# variable.
|
metadata
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: halorgium-dm-is-state_machine
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 1012425149
|
5
|
+
prerelease: 7
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 10
|
9
|
+
- 2
|
10
|
+
- via
|
11
|
+
version: 0.10.2.via
|
12
|
+
platform: ruby
|
13
|
+
authors:
|
14
|
+
- David James
|
15
|
+
autorequire:
|
16
|
+
bindir: bin
|
17
|
+
cert_chain: []
|
18
|
+
|
19
|
+
date: 2009-12-11 00:00:00 -08:00
|
20
|
+
default_executable:
|
21
|
+
dependencies:
|
22
|
+
- !ruby/object:Gem::Dependency
|
23
|
+
name: dm-core
|
24
|
+
prerelease: false
|
25
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - ~>
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
hash: 51
|
31
|
+
segments:
|
32
|
+
- 0
|
33
|
+
- 10
|
34
|
+
- 2
|
35
|
+
version: 0.10.2
|
36
|
+
type: :runtime
|
37
|
+
version_requirements: *id001
|
38
|
+
- !ruby/object:Gem::Dependency
|
39
|
+
name: rspec
|
40
|
+
prerelease: false
|
41
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ~>
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
hash: 13
|
47
|
+
segments:
|
48
|
+
- 1
|
49
|
+
- 2
|
50
|
+
- 9
|
51
|
+
version: 1.2.9
|
52
|
+
type: :development
|
53
|
+
version_requirements: *id002
|
54
|
+
- !ruby/object:Gem::Dependency
|
55
|
+
name: yard
|
56
|
+
prerelease: false
|
57
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ~>
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
hash: 15
|
63
|
+
segments:
|
64
|
+
- 0
|
65
|
+
- 4
|
66
|
+
- 0
|
67
|
+
version: 0.4.0
|
68
|
+
type: :development
|
69
|
+
version_requirements: *id003
|
70
|
+
description: DataMapper plugin for creating state machines
|
71
|
+
email: djwonk [a] collectiveinsight [d] net
|
72
|
+
executables: []
|
73
|
+
|
74
|
+
extensions: []
|
75
|
+
|
76
|
+
extra_rdoc_files:
|
77
|
+
- LICENSE
|
78
|
+
- README.rdoc
|
79
|
+
files:
|
80
|
+
- lib/dm-is-state_machine.rb
|
81
|
+
- lib/dm-is-state_machine/is/data/event.rb
|
82
|
+
- lib/dm-is-state_machine/is/data/machine.rb
|
83
|
+
- lib/dm-is-state_machine/is/data/state.rb
|
84
|
+
- lib/dm-is-state_machine/is/dsl/event_dsl.rb
|
85
|
+
- lib/dm-is-state_machine/is/dsl/state_dsl.rb
|
86
|
+
- lib/dm-is-state_machine/is/state_machine.rb
|
87
|
+
- LICENSE
|
88
|
+
- README.rdoc
|
89
|
+
has_rdoc: true
|
90
|
+
homepage: http://github.com/halorgium/dm-is-state_machine/tree/via
|
91
|
+
licenses: []
|
92
|
+
|
93
|
+
post_install_message:
|
94
|
+
rdoc_options:
|
95
|
+
- --charset=UTF-8
|
96
|
+
require_paths:
|
97
|
+
- lib
|
98
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
99
|
+
none: false
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
hash: 3
|
104
|
+
segments:
|
105
|
+
- 0
|
106
|
+
version: "0"
|
107
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
108
|
+
none: false
|
109
|
+
requirements:
|
110
|
+
- - ">="
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
hash: 3
|
113
|
+
segments:
|
114
|
+
- 0
|
115
|
+
version: "0"
|
116
|
+
requirements: []
|
117
|
+
|
118
|
+
rubyforge_project: datamapper
|
119
|
+
rubygems_version: 1.5.0
|
120
|
+
signing_key:
|
121
|
+
specification_version: 3
|
122
|
+
summary: DataMapper plugin for creating state machines
|
123
|
+
test_files: []
|
124
|
+
|