stateful.rb 2.1.0 → 2.3.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +161 -0
- data/Gemfile +3 -0
- data/README.md +309 -0
- data/lib/Stateful/ActiveRecord/ClassMethods.rb +7 -65
- data/lib/Stateful/ClassMethods.rb +60 -0
- data/lib/Stateful/Poro/ClassMethods.rb +12 -70
- data/lib/Stateful/Sequel/ClassMethods.rb +59 -0
- data/lib/Stateful/Sequel.rb +30 -0
- data/lib/Stateful/VERSION.rb +1 -1
- data/lib/Stateful.rb +3 -0
- data/stateful.rb.gemspec +42 -0
- data/test/ActiveRecord/WithColumnName.rb +159 -0
- data/test/ActiveRecord/WithExplicitDeterministicEventOrdering.rb +159 -0
- data/test/ActiveRecord/WithExplicitGlobalNonDeterministicEventOrdering.rb +165 -0
- data/test/ActiveRecord/WithExplicitGlobalNonDeterministicEventOrderingAndInitialStateBlock.rb +163 -0
- data/test/ActiveRecord/WithExplicitNonDeterministicEventOrdering.rb +161 -0
- data/test/ActiveRecord/WithInitialStateBlock.rb +157 -0
- data/test/ActiveRecord/WithMultipleFinalStates.rb +188 -0
- data/test/ActiveRecord/WithMultipleStateMachines.rb +221 -0
- data/test/ActiveRecord/WithMultipleStateMachinesAndStatefulBlock.rb +223 -0
- data/test/ActiveRecord/WithPersistenceNonSpecificExtend.rb +159 -0
- data/test/ActiveRecord/WithPersistenceNonSpecificExtendAndStatefulBlock.rb +161 -0
- data/test/ActiveRecord/WithPersistenceSpecificExtend.rb +159 -0
- data/test/ActiveRecord/WithPersistenceSpecificExtendAndStatefulBlock.rb +161 -0
- data/test/ActiveRecord/test_helper.rb +9 -0
- data/test/ActiveRecord.rb +5 -0
- data/test/Poro/WithExplicitDeterministicEventOrdering.rb +147 -0
- data/test/Poro/WithExplicitGlobalNonDeterministicEventOrdering.rb +153 -0
- data/test/Poro/WithExplicitGlobalNonDeterministicEventOrderingAndInitialStateBlock.rb +151 -0
- data/test/Poro/WithExplicitNonDeterministicEventOrdering.rb +149 -0
- data/test/Poro/WithInitialStateBlock.rb +145 -0
- data/test/Poro/WithMultipleFinalStates.rb +176 -0
- data/test/Poro/WithMultipleStateMachines.rb +192 -0
- data/test/Poro/WithMultipleStateMachinesAndStatefulBlock.rb +194 -0
- data/test/Poro/WithPersistenceNonSpecificExtend.rb +147 -0
- data/test/Poro/WithPersistenceNonSpecificExtendAndStatefulBlock.rb +149 -0
- data/test/Poro/WithPersistenceSpecificExtend.rb +147 -0
- data/test/Poro/WithPersistenceSpecificExtendAndStatefulBlock.rb +149 -0
- data/test/Poro/WithVariableName.rb +147 -0
- data/test/Poro.rb +5 -0
- data/test/Sequel/WithColumnName.rb +154 -0
- data/test/Sequel/WithExplicitDeterministicEventOrdering.rb +154 -0
- data/test/Sequel/WithExplicitGlobalNonDeterministicEventOrdering.rb +160 -0
- data/test/Sequel/WithExplicitGlobalNonDeterministicEventOrderingAndInitialStateBlock.rb +158 -0
- data/test/Sequel/WithExplicitNonDeterministicEventOrdering.rb +156 -0
- data/test/Sequel/WithInitialStateBlock.rb +152 -0
- data/test/Sequel/WithMultipleFinalStates.rb +183 -0
- data/test/Sequel/WithMultipleStateMachines.rb +216 -0
- data/test/Sequel/WithMultipleStateMachinesAndStatefulBlock.rb +218 -0
- data/test/Sequel/WithPersistenceNonSpecificExtend.rb +154 -0
- data/test/Sequel/WithPersistenceNonSpecificExtendAndStatefulBlock.rb +156 -0
- data/test/Sequel/WithPersistenceSpecificExtend.rb +154 -0
- data/test/Sequel/WithPersistenceSpecificExtendAndStatefulBlock.rb +156 -0
- data/test/Sequel/test_helper.rb +5 -0
- data/test/Sequel.rb +5 -0
- data/test/Stateful.rb +15 -0
- metadata +69 -4
|
@@ -6,8 +6,8 @@ module Stateful
|
|
|
6
6
|
module ClassMethods
|
|
7
7
|
class << self
|
|
8
8
|
def extended(klass)
|
|
9
|
-
klass.
|
|
10
|
-
klass.
|
|
9
|
+
klass.define_stateful_attribute_setter_method
|
|
10
|
+
klass.define_stateful_attribute_getter_method
|
|
11
11
|
klass.define_next_state_method
|
|
12
12
|
klass.define_transitions_method
|
|
13
13
|
klass.define_initial_stateQ_method
|
|
@@ -15,15 +15,6 @@ module Stateful
|
|
|
15
15
|
end
|
|
16
16
|
end # class << self
|
|
17
17
|
|
|
18
|
-
def define_named_machine_methods(machine_name)
|
|
19
|
-
define_stateful_variable_name_setter_method(machine_name: machine_name)
|
|
20
|
-
define_stateful_variable_name_getter_method(machine_name: machine_name)
|
|
21
|
-
define_next_state_method(machine_name: machine_name)
|
|
22
|
-
define_transitions_method(machine_name: machine_name)
|
|
23
|
-
define_initial_stateQ_method(machine_name: machine_name)
|
|
24
|
-
define_final_stateQ_method(machine_name: machine_name)
|
|
25
|
-
end
|
|
26
|
-
|
|
27
18
|
def stateful_variable_name=(stateful_variable_name)
|
|
28
19
|
@stateful_variable_name = stateful_variable_name
|
|
29
20
|
end
|
|
@@ -32,7 +23,7 @@ module Stateful
|
|
|
32
23
|
@stateful_variable_name
|
|
33
24
|
end
|
|
34
25
|
|
|
35
|
-
def
|
|
26
|
+
def stateful_attribute_name_for_machine(machine_name = nil)
|
|
36
27
|
if machine_name
|
|
37
28
|
"#{machine_name}_state"
|
|
38
29
|
else
|
|
@@ -40,74 +31,25 @@ module Stateful
|
|
|
40
31
|
end
|
|
41
32
|
end
|
|
42
33
|
|
|
43
|
-
def
|
|
44
|
-
|
|
45
|
-
define_method(
|
|
46
|
-
|
|
47
|
-
next_state = self.class.stateful_state_machine(machine_name).find(next_state_name)
|
|
48
|
-
self.send("#{variable_name}=", next_state)
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
def define_status_predicate_method(state_name, machine_name: nil)
|
|
53
|
-
variable_name = variable_name_for_machine(machine_name)
|
|
54
|
-
define_method("#{state_name}?") do
|
|
55
|
-
self.send(variable_name).name == state_name
|
|
34
|
+
def define_stateful_attribute_setter_method(machine_name: nil)
|
|
35
|
+
attribute_name = stateful_attribute_name_for_machine(machine_name)
|
|
36
|
+
define_method("#{attribute_name}=") do |state|
|
|
37
|
+
instance_variable_set("@#{attribute_name}", self.class.stateful_state_machine(machine_name).find(state))
|
|
56
38
|
end
|
|
57
39
|
end
|
|
58
40
|
|
|
59
|
-
def
|
|
60
|
-
|
|
61
|
-
define_method(
|
|
62
|
-
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
def define_stateful_variable_name_getter_method(machine_name: nil)
|
|
67
|
-
variable_name = variable_name_for_machine(machine_name)
|
|
68
|
-
define_method(variable_name) do
|
|
69
|
-
if state = instance_variable_get("@#{variable_name}")
|
|
41
|
+
def define_stateful_attribute_getter_method(machine_name: nil)
|
|
42
|
+
attribute_name = stateful_attribute_name_for_machine(machine_name)
|
|
43
|
+
define_method(attribute_name) do
|
|
44
|
+
if state = instance_variable_get("@#{attribute_name}")
|
|
70
45
|
state
|
|
71
46
|
else
|
|
72
47
|
initial_state = self.class.stateful_state_machine(machine_name).initial_state
|
|
73
|
-
self.send("#{
|
|
48
|
+
self.send("#{attribute_name}=", initial_state.name)
|
|
74
49
|
initial_state
|
|
75
50
|
end
|
|
76
51
|
end
|
|
77
52
|
end
|
|
78
|
-
|
|
79
|
-
def define_next_state_method(machine_name: nil)
|
|
80
|
-
variable_name = variable_name_for_machine(machine_name)
|
|
81
|
-
method_name = machine_name ? "#{machine_name}_next_state" : :next_state
|
|
82
|
-
define_method(method_name) do |event_name|
|
|
83
|
-
next_state_name = self.send(variable_name).next_state_name(event_name)
|
|
84
|
-
self.class.stateful_state_machine(machine_name).find(next_state_name)
|
|
85
|
-
end
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
def define_transitions_method(machine_name: nil)
|
|
89
|
-
variable_name = variable_name_for_machine(machine_name)
|
|
90
|
-
method_name = machine_name ? "#{machine_name}_transitions" : :transitions
|
|
91
|
-
define_method(method_name) do
|
|
92
|
-
self.send(variable_name).transitions
|
|
93
|
-
end
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
def define_initial_stateQ_method(machine_name: nil)
|
|
97
|
-
variable_name = variable_name_for_machine(machine_name)
|
|
98
|
-
method_name = machine_name ? "#{machine_name}_initial_state?" : :initial_state?
|
|
99
|
-
define_method(method_name) do
|
|
100
|
-
self.send(variable_name) == self.class.stateful_state_machine(machine_name).initial_state
|
|
101
|
-
end
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
def define_final_stateQ_method(machine_name: nil)
|
|
105
|
-
variable_name = variable_name_for_machine(machine_name)
|
|
106
|
-
method_name = machine_name ? "#{machine_name}_final_state?" : :final_state?
|
|
107
|
-
define_method(method_name) do
|
|
108
|
-
self.class.stateful_state_machine(machine_name).final_states.include?(self.send(variable_name))
|
|
109
|
-
end
|
|
110
|
-
end
|
|
111
53
|
end
|
|
112
54
|
end
|
|
113
55
|
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Stateful/Sequel/ClassMethods.rb
|
|
2
|
+
# Stateful::Sequel::ClassMethods
|
|
3
|
+
|
|
4
|
+
module Stateful
|
|
5
|
+
module Sequel
|
|
6
|
+
module ClassMethods
|
|
7
|
+
class << self
|
|
8
|
+
def extended(klass)
|
|
9
|
+
klass.define_stateful_attribute_setter_method
|
|
10
|
+
klass.define_stateful_attribute_getter_method
|
|
11
|
+
klass.define_next_state_method
|
|
12
|
+
klass.define_transitions_method
|
|
13
|
+
klass.define_initial_stateQ_method
|
|
14
|
+
klass.define_final_stateQ_method
|
|
15
|
+
end
|
|
16
|
+
end # class << self
|
|
17
|
+
|
|
18
|
+
def stateful_column_name=(stateful_column_name)
|
|
19
|
+
@stateful_column_name = stateful_column_name
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def stateful_column_name
|
|
23
|
+
@stateful_column_name
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def stateful_attribute_name_for_machine(machine_name = nil)
|
|
27
|
+
if machine_name
|
|
28
|
+
"#{machine_name}_state"
|
|
29
|
+
else
|
|
30
|
+
instance_variable_get(:@stateful_column_name)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def define_stateful_attribute_setter_method(machine_name: nil)
|
|
35
|
+
stateful_column_name = stateful_attribute_name_for_machine(machine_name)
|
|
36
|
+
define_method("#{stateful_column_name}=") do |state|
|
|
37
|
+
instance_variable_set("@#{stateful_column_name}", self.class.stateful_state_machine(machine_name).find(state))
|
|
38
|
+
self[stateful_column_name.to_sym] = instance_variable_get("@#{stateful_column_name}").name.to_s
|
|
39
|
+
self.save
|
|
40
|
+
instance_variable_get("@#{stateful_column_name}")
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def define_stateful_attribute_getter_method(machine_name: nil)
|
|
45
|
+
stateful_column_name = stateful_attribute_name_for_machine(machine_name)
|
|
46
|
+
define_method(stateful_column_name) do
|
|
47
|
+
instance_variable_set("@#{stateful_column_name}", self.class.stateful_state_machine(machine_name).find(self[stateful_column_name.to_sym]))
|
|
48
|
+
if state = instance_variable_get("@#{stateful_column_name}")
|
|
49
|
+
state
|
|
50
|
+
else
|
|
51
|
+
initial_state = self.class.stateful_state_machine(machine_name).initial_state
|
|
52
|
+
self.send("#{stateful_column_name}=", initial_state.name)
|
|
53
|
+
initial_state
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Stateful/Sequel.rb
|
|
2
|
+
# Stateful::Sequel
|
|
3
|
+
|
|
4
|
+
require_relative 'ClassMethods'
|
|
5
|
+
require_relative 'InstanceMethods'
|
|
6
|
+
require_relative File.join('Sequel', 'ClassMethods')
|
|
7
|
+
|
|
8
|
+
module Stateful
|
|
9
|
+
module Sequel
|
|
10
|
+
|
|
11
|
+
class << self
|
|
12
|
+
|
|
13
|
+
def set_column_name(klass)
|
|
14
|
+
unless klass.instance_variable_get(:@stateful_column_name)
|
|
15
|
+
klass.instance_variable_set(:@stateful_column_name, 'current_state')
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def extended(klass)
|
|
20
|
+
klass.extend(Stateful::ClassMethods)
|
|
21
|
+
klass.send(:include, Stateful::InstanceMethods)
|
|
22
|
+
set_column_name(klass)
|
|
23
|
+
klass.extend(Stateful::Sequel::ClassMethods)
|
|
24
|
+
end
|
|
25
|
+
alias_method :included, :extended
|
|
26
|
+
|
|
27
|
+
end # class << self
|
|
28
|
+
|
|
29
|
+
end
|
|
30
|
+
end
|
data/lib/Stateful/VERSION.rb
CHANGED
data/lib/Stateful.rb
CHANGED
|
@@ -9,6 +9,9 @@ module Stateful
|
|
|
9
9
|
if Object.const_defined?(:ActiveRecord) && defined?(::ActiveRecord::Base) && klass < ::ActiveRecord::Base
|
|
10
10
|
require_relative File.join('Stateful', 'ActiveRecord')
|
|
11
11
|
klass.extend(Stateful::ActiveRecord)
|
|
12
|
+
elsif Object.const_defined?(:Sequel) && defined?(::Sequel::Model) && klass < ::Sequel::Model
|
|
13
|
+
require_relative File.join('Stateful', 'Sequel')
|
|
14
|
+
klass.extend(Stateful::Sequel)
|
|
12
15
|
else
|
|
13
16
|
require_relative File.join('Stateful', 'Poro')
|
|
14
17
|
klass.extend(Stateful::Poro)
|
data/stateful.rb.gemspec
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
require_relative './lib/Stateful/VERSION'
|
|
2
|
+
|
|
3
|
+
class Gem::Specification
|
|
4
|
+
def development_dependencies=(gems)
|
|
5
|
+
gems.each{|gem| add_development_dependency(*gem)}
|
|
6
|
+
end
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
Gem::Specification.new do |spec|
|
|
10
|
+
spec.name = 'stateful.rb'
|
|
11
|
+
spec.version = Stateful::VERSION
|
|
12
|
+
|
|
13
|
+
spec.summary = "A Ruby state machine."
|
|
14
|
+
spec.description = "Easily add state to Poro, ActiveRecord, and Sequel objects."
|
|
15
|
+
|
|
16
|
+
spec.author = 'thoran'
|
|
17
|
+
spec.email = 'code@thoran.com'
|
|
18
|
+
spec.homepage = 'http://github.com/thoran/stateful'
|
|
19
|
+
spec.license = 'MIT'
|
|
20
|
+
|
|
21
|
+
# The required version of Ruby is this higher than it strictly needs to be solely because the migrations have been made to require AR 6.0, but otherwise this should work with much lower versions of Ruby and ActiveRecord, even down to 2.3 (or lower) and AR 3 (or lower).
|
|
22
|
+
spec.required_ruby_version = '>= 2.5'
|
|
23
|
+
|
|
24
|
+
spec.require_paths = ['lib']
|
|
25
|
+
|
|
26
|
+
spec.files = [
|
|
27
|
+
'stateful.rb.gemspec',
|
|
28
|
+
'CHANGELOG.md',
|
|
29
|
+
'Gemfile',
|
|
30
|
+
'README.md',
|
|
31
|
+
Dir['lib/**/*.rb'],
|
|
32
|
+
Dir['test/**/*.rb']
|
|
33
|
+
].flatten
|
|
34
|
+
|
|
35
|
+
spec.development_dependencies = %w{
|
|
36
|
+
minitest
|
|
37
|
+
minitest-spec-context
|
|
38
|
+
activerecord
|
|
39
|
+
sequel
|
|
40
|
+
sqlite3
|
|
41
|
+
}
|
|
42
|
+
end
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# test/ActiveRecord/WithColumnName.rb
|
|
2
|
+
|
|
3
|
+
gem 'minitest'
|
|
4
|
+
gem 'minitest-spec-context'
|
|
5
|
+
|
|
6
|
+
require 'minitest/autorun'
|
|
7
|
+
require 'minitest-spec-context'
|
|
8
|
+
|
|
9
|
+
require_relative './test_helper'
|
|
10
|
+
|
|
11
|
+
lib_dir = File.expand_path(File.join(__FILE__, '..', '..', '..', 'lib'))
|
|
12
|
+
$LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir)
|
|
13
|
+
|
|
14
|
+
require 'Stateful'
|
|
15
|
+
|
|
16
|
+
class CreateTableMachines < ActiveRecord::Migration[6.0]
|
|
17
|
+
def change
|
|
18
|
+
create_table :active_record_machine8s do |t|
|
|
19
|
+
t.string :status
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
class ActiveRecordMachine8 < ActiveRecord::Base
|
|
25
|
+
@stateful_column_name = 'status'
|
|
26
|
+
|
|
27
|
+
extend Stateful
|
|
28
|
+
|
|
29
|
+
initial_state :initial_state do
|
|
30
|
+
on :an_event => :next_state
|
|
31
|
+
on :another_event => :final_state
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
state :next_state do
|
|
35
|
+
on :yet_another_event => :final_state
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
final_state :final_state
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
CreateTableMachines.new.change
|
|
42
|
+
|
|
43
|
+
describe Stateful::ActiveRecord do
|
|
44
|
+
let(:machine){ActiveRecordMachine8.create}
|
|
45
|
+
|
|
46
|
+
it "must have an initial state" do
|
|
47
|
+
_(machine.initial_state).wont_be_nil
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it "must have a final state (if one has been specified)" do
|
|
51
|
+
if ActiveRecordMachine8.final_state?
|
|
52
|
+
_(machine.final_state).wont_be_nil
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
context "initial_state" do
|
|
57
|
+
it "must have an initial state before any events occur" do
|
|
58
|
+
_(machine.initial_state?).must_equal true
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it "must have an initial state consistent with what is given" do
|
|
62
|
+
_(machine.initial_state).must_equal ActiveRecordMachine8.stateful_state_machine.initial_state
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it "must have an initial state with name as per the name given" do
|
|
66
|
+
_(machine.initial_state.name).must_equal :initial_state
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it "must not be in the final state (assuming that initial and final are different states)" do
|
|
70
|
+
_(machine.final_state?).must_equal false
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it "must know what it's next state is given an event name" do
|
|
74
|
+
_(machine.next_state(:an_event)).must_equal ActiveRecordMachine8.stateful_state_machine.find(:next_state)
|
|
75
|
+
_(machine.next_state(:another_event)).must_equal ActiveRecordMachine8.stateful_state_machine.find(:final_state)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
it "must have an intial state which has as set of transitions to other states" do
|
|
79
|
+
_(machine.transitions.class).must_equal Array
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it "must have two transitions to other states" do
|
|
83
|
+
_(machine.transitions.size).must_equal 2
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
it "must know what transitions are available" do
|
|
87
|
+
_(machine.transitions.collect{|t| [t.event_name, t.next_state_name]}).must_equal [[:an_event, :next_state], [:another_event, :final_state]]
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
it "must be active" do
|
|
91
|
+
_(machine.active?).must_equal true
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
context "next_state" do
|
|
96
|
+
before do
|
|
97
|
+
machine.an_event
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
it "must not be in the initial state" do
|
|
101
|
+
_(machine.initial_state?).must_equal false
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
it "must not be in the final state" do
|
|
105
|
+
_(machine.final_state?).must_equal false
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
it "must be in the next_state state" do
|
|
109
|
+
_(machine.status.name).must_equal :next_state
|
|
110
|
+
_(machine.next_state?).must_equal true
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
it "must have a set of transitions to other states" do
|
|
114
|
+
_(machine.transitions.class).must_equal Array
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
it "must have one transition to other states" do
|
|
118
|
+
_(machine.transitions.size).must_equal 1
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
it "must know what transitions are available" do
|
|
122
|
+
_(machine.transitions.collect{|t| [t.event_name, t.next_state_name]}).must_equal [[:yet_another_event, :final_state]]
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
it "must be active" do
|
|
126
|
+
_(machine.active?).must_equal true
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
context "final_state" do
|
|
131
|
+
before do
|
|
132
|
+
machine.another_event
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
it "must have a final state" do
|
|
136
|
+
_(machine.final_state?).must_equal true
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
it "must have a final state consistent with what is given" do
|
|
140
|
+
_(machine.final_state).must_equal ActiveRecordMachine8.stateful_state_machine.final_state
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
it "must have a state with name as per the name given" do
|
|
144
|
+
_(machine.status.name).must_equal :final_state
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
it "must not be in the initial state (assuming that initial and final are different states)" do
|
|
148
|
+
_(machine.initial_state?).must_equal false
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
it "must have no transitions" do
|
|
152
|
+
_(machine.transitions.empty?).must_equal true
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
it "must not be active" do
|
|
156
|
+
_(machine.active?).must_equal false
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# test/ActiveRecord/WithExplicitDeterministicEventOrdering.rb
|
|
2
|
+
|
|
3
|
+
gem 'minitest'
|
|
4
|
+
gem 'minitest-spec-context'
|
|
5
|
+
|
|
6
|
+
require 'minitest/autorun'
|
|
7
|
+
require 'minitest-spec-context'
|
|
8
|
+
|
|
9
|
+
require_relative './test_helper'
|
|
10
|
+
|
|
11
|
+
lib_dir = File.expand_path(File.join(__FILE__, '..', '..', '..', 'lib'))
|
|
12
|
+
$LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir)
|
|
13
|
+
|
|
14
|
+
require 'Stateful'
|
|
15
|
+
|
|
16
|
+
class CreateTableMachines < ActiveRecord::Migration[6.0]
|
|
17
|
+
def change
|
|
18
|
+
create_table :active_record_machine5s do |t|
|
|
19
|
+
t.string :current_state
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
class ActiveRecordMachine5 < ActiveRecord::Base
|
|
25
|
+
extend Stateful
|
|
26
|
+
|
|
27
|
+
initial_state :initial_state, deterministic: true do
|
|
28
|
+
on :an_event => :next_state
|
|
29
|
+
on :another_event => :final_state
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
state :next_state do
|
|
33
|
+
on :yet_another_event => :final_state
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
final_state :final_state
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
CreateTableMachines.new.change
|
|
40
|
+
|
|
41
|
+
describe Stateful::ActiveRecord do
|
|
42
|
+
let(:machine){ActiveRecordMachine5.create}
|
|
43
|
+
|
|
44
|
+
it "must have an initial state" do
|
|
45
|
+
_(machine.initial_state).wont_be_nil
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it "must have a final state (if one has been specified)" do
|
|
49
|
+
if ActiveRecordMachine5.final_state?
|
|
50
|
+
_(machine.final_state).wont_be_nil
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
context "initial_state" do
|
|
55
|
+
it "must have an initial state before any events occur" do
|
|
56
|
+
_(machine.initial_state?).must_equal true
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it "must have an initial state consistent with what is given" do
|
|
60
|
+
_(machine.initial_state).must_equal ActiveRecordMachine5.stateful_state_machine.initial_state
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it "must have an initial state with name as per the name given" do
|
|
64
|
+
_(machine.initial_state.name).must_equal :initial_state
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it "must not be in the final state (assuming that initial and final are different states)" do
|
|
68
|
+
_(machine.final_state?).must_equal false
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
it "must know what it's next state is given an event name" do
|
|
72
|
+
_(machine.next_state(:an_event)).must_equal ActiveRecordMachine5.stateful_state_machine.find(:next_state)
|
|
73
|
+
_(machine.next_state(:another_event)).must_equal ActiveRecordMachine5.stateful_state_machine.find(:final_state)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
it "must have an intial state which has a collection of transitions to other states" do
|
|
77
|
+
_(machine.transitions.class).must_equal Array
|
|
78
|
+
_(machine.transitions.all?{|transition| transition.is_a?(Stateful::Transition)}).must_equal true
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
it "must have two transitions to other states" do
|
|
82
|
+
_(machine.transitions.size).must_equal 2
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it "must know what transitions are available and in what order they are presented" do
|
|
86
|
+
_(machine.transitions.collect{|t| [t.event_name, t.next_state_name]}).must_equal [[:an_event, :next_state], [:another_event, :final_state]]
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
it "must be active" do
|
|
90
|
+
_(machine.active?).must_equal true
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
context "next_state" do
|
|
95
|
+
before do
|
|
96
|
+
machine.an_event
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
it "must not be in the initial state" do
|
|
100
|
+
_(machine.initial_state?).must_equal false
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
it "must not be in the final state" do
|
|
104
|
+
_(machine.final_state?).must_equal false
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
it "must be in the next_state state" do
|
|
108
|
+
_(machine.current_state.name).must_equal :next_state
|
|
109
|
+
_(machine.next_state?).must_equal true
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
it "must have a collection of transitions to other states" do
|
|
113
|
+
_(machine.transitions.class).must_equal Array
|
|
114
|
+
_(machine.transitions.all?{|transition| transition.is_a?(Stateful::Transition)}).must_equal true
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
it "must have one transition to other states" do
|
|
118
|
+
_(machine.transitions.size).must_equal 1
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
it "must know what transitions are available" do
|
|
122
|
+
_(machine.transitions.collect{|t| [t.event_name, t.next_state_name]}).must_equal [[:yet_another_event, :final_state]]
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
it "must be active" do
|
|
126
|
+
_(machine.active?).must_equal true
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
context "final_state" do
|
|
131
|
+
before do
|
|
132
|
+
machine.another_event
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
it "must have a final state" do
|
|
136
|
+
_(machine.final_state?).must_equal true
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
it "must have a final state consistent with what is given" do
|
|
140
|
+
_(machine.final_state).must_equal ActiveRecordMachine5.stateful_state_machine.final_state
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
it "must have a state with name as per the name given" do
|
|
144
|
+
_(machine.current_state.name).must_equal :final_state
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
it "must not be in the initial state (assuming that initial and final are different states)" do
|
|
148
|
+
_(machine.initial_state?).must_equal false
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
it "must have no transitions" do
|
|
152
|
+
_(machine.transitions.empty?).must_equal true
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
it "must not be active" do
|
|
156
|
+
_(machine.active?).must_equal false
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|