simple_state_machine 0.5.2 → 0.6.1
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 +7 -0
- data/.github/workflows/build.yml +46 -0
- data/.gitignore +3 -0
- data/.travis.yml +17 -0
- data/Changelog.rdoc +16 -0
- data/Gemfile +7 -0
- data/README.rdoc +154 -99
- data/examples/conversation.rb +5 -5
- data/examples/lamp.rb +5 -5
- data/examples/relationship.rb +8 -8
- data/examples/traffic_light.rb +3 -3
- data/examples/user.rb +8 -4
- data/gemfiles/Gemfile.activerecord-5.2.x +9 -0
- data/gemfiles/Gemfile.activerecord-6.0.x +9 -0
- data/gemfiles/Gemfile.activerecord-6.1.x +8 -0
- data/gemfiles/Gemfile.activerecord-main.x +9 -0
- data/gemfiles/Gemfile.basic +6 -0
- data/lib/simple_state_machine/.DS_Store +0 -0
- data/lib/simple_state_machine/active_record.rb +2 -64
- data/lib/simple_state_machine/decorator/active_record.rb +68 -0
- data/lib/simple_state_machine/decorator/default.rb +91 -0
- data/lib/simple_state_machine/railtie.rb +1 -1
- data/lib/simple_state_machine/simple_state_machine.rb +8 -251
- data/lib/simple_state_machine/state_machine.rb +88 -0
- data/lib/simple_state_machine/state_machine_definition.rb +72 -0
- data/lib/simple_state_machine/tools/graphviz.rb +21 -0
- data/lib/simple_state_machine/tools/inspector.rb +44 -0
- data/lib/simple_state_machine/transition.rb +40 -0
- data/lib/simple_state_machine/version.rb +1 -1
- data/lib/simple_state_machine.rb +13 -3
- data/lib/tasks/graphviz.rake +31 -0
- data/simple_state_machine.gemspec +14 -24
- data/spec/.DS_Store +0 -0
- data/spec/active_record_spec.rb +216 -179
- data/spec/{decorator_spec.rb → decorator/default_spec.rb} +32 -32
- data/spec/examples_spec.rb +17 -17
- data/spec/mountable_spec.rb +26 -14
- data/spec/simple_state_machine_spec.rb +128 -92
- data/spec/spec_helper.rb +18 -5
- data/spec/state_machine_definition_spec.rb +48 -34
- data/spec/state_machine_spec.rb +36 -2
- data/spec/tools/graphviz_spec.rb +30 -0
- data/spec/tools/inspector_spec.rb +70 -0
- metadata +54 -128
- data/autotest/discover.rb +0 -1
- data/lib/tasks/graphiz.rake +0 -13
- data/rails/graphiz.rake +0 -16
@@ -0,0 +1,21 @@
|
|
1
|
+
module SimpleStateMachine
|
2
|
+
module Tools
|
3
|
+
require 'cgi'
|
4
|
+
module Graphviz
|
5
|
+
# Graphviz dot format for rendering as a directional graph
|
6
|
+
def to_graphviz_dot
|
7
|
+
"digraph G {\n" +
|
8
|
+
transitions.map { |t| t.to_graphviz_dot }.sort.join(";\n") +
|
9
|
+
"\n}"
|
10
|
+
end
|
11
|
+
|
12
|
+
# Generates a url that renders states and events as a directional graph.
|
13
|
+
# See http://code.google.com/apis/chart/docs/gallery/graphviz.html
|
14
|
+
def google_chart_url
|
15
|
+
graph = transitions.map { |t| t.to_graphviz_dot }.sort.join(";")
|
16
|
+
puts graph
|
17
|
+
"http://chart.googleapis.com/chart?cht=gv&chl=digraph{#{::CGI.escape graph}}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module SimpleStateMachine
|
2
|
+
module Tools
|
3
|
+
module Inspector
|
4
|
+
def begin_states
|
5
|
+
from_states - to_states
|
6
|
+
end
|
7
|
+
|
8
|
+
def end_states
|
9
|
+
to_states - from_states
|
10
|
+
end
|
11
|
+
|
12
|
+
def states
|
13
|
+
(to_states + from_states).uniq
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def from_states
|
19
|
+
to_uniq_sym(sample_transitions.map(&:from))
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_states
|
23
|
+
to_uniq_sym(sample_transitions.map(&:to))
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_uniq_sym(array)
|
27
|
+
array.map { |state| state.is_a?(String) ? state.to_sym : state }.uniq
|
28
|
+
end
|
29
|
+
|
30
|
+
def sample_transitions
|
31
|
+
(@subject || sample_subject).state_machine_definition.send :transitions
|
32
|
+
end
|
33
|
+
|
34
|
+
def sample_subject
|
35
|
+
self_class = self.class
|
36
|
+
sample = Class.new do
|
37
|
+
extend SimpleStateMachine::Mountable
|
38
|
+
mount_state_machine self_class
|
39
|
+
end
|
40
|
+
sample
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module SimpleStateMachine
|
2
|
+
# Defines transitions for events
|
3
|
+
class Transition
|
4
|
+
attr_reader :event_name, :from, :to
|
5
|
+
def initialize(event_name, from, to)
|
6
|
+
@event_name = event_name.to_s
|
7
|
+
@from = from.is_a?(Class) ? from : from.to_s
|
8
|
+
@to = to.to_s
|
9
|
+
end
|
10
|
+
|
11
|
+
# returns true if it's a transition for event_name and subject_state
|
12
|
+
def is_transition_for?(event_name, subject_state)
|
13
|
+
is_same_event?(event_name) && is_same_from?(subject_state)
|
14
|
+
end
|
15
|
+
|
16
|
+
# returns true if it's a error transition for event_name and error
|
17
|
+
def is_error_transition_for?(event_name, error)
|
18
|
+
is_same_event?(event_name) && from.is_a?(Class) && error.is_a?(from)
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_s
|
22
|
+
"#{from}.#{event_name}! => #{to}"
|
23
|
+
end
|
24
|
+
|
25
|
+
# TODO move to Graphiz module
|
26
|
+
def to_graphviz_dot
|
27
|
+
%("#{from}"->"#{to}"[label=#{event_name}])
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def is_same_event?(event_name)
|
33
|
+
self.event_name == event_name.to_s
|
34
|
+
end
|
35
|
+
|
36
|
+
def is_same_from?(subject_from)
|
37
|
+
from.to_s == 'all' || subject_from.to_s == from.to_s
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/simple_state_machine.rb
CHANGED
@@ -1,4 +1,14 @@
|
|
1
1
|
require 'simple_state_machine/simple_state_machine'
|
2
|
-
require 'simple_state_machine/
|
3
|
-
require
|
4
|
-
|
2
|
+
require 'simple_state_machine/state_machine'
|
3
|
+
require 'simple_state_machine/tools/graphviz'
|
4
|
+
require 'simple_state_machine/tools/inspector'
|
5
|
+
require 'simple_state_machine/state_machine_definition'
|
6
|
+
require 'simple_state_machine/transition'
|
7
|
+
require 'simple_state_machine/decorator/default'
|
8
|
+
if defined?(ActiveRecord)
|
9
|
+
require 'simple_state_machine/active_record'
|
10
|
+
require 'simple_state_machine/decorator/active_record'
|
11
|
+
end
|
12
|
+
if defined?(Rails::Railtie)
|
13
|
+
require "simple_state_machine/railtie"
|
14
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
namespace :ssm do
|
2
|
+
namespace :graph do
|
3
|
+
|
4
|
+
desc 'Outputs a dot file. You must specify class=ClassNAME'
|
5
|
+
task :dot => :environment do
|
6
|
+
if clazz = ENV['class']
|
7
|
+
puts clazz.constantize.state_machine_definition.to_graphviz_dot
|
8
|
+
else
|
9
|
+
puts "Missing argument: class. Please specify class=ClassName"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
desc 'Generate a url for a google chart. You must specify class=ClassName'
|
14
|
+
task :url => :environment do
|
15
|
+
if clazz = ENV['class']
|
16
|
+
puts clazz.constantize.state_machine_definition.google_chart_url
|
17
|
+
else
|
18
|
+
puts "Missing argument: class. Please specify class=ClassName"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
desc 'Opens the google chart in your browser. You must specify class=ClassNAME'
|
23
|
+
task :open => :environment do
|
24
|
+
if clazz = ENV['class']
|
25
|
+
`open '#{::CGI.unescape(clazz.constantize.state_machine_definition.google_chart_url)}'`
|
26
|
+
else
|
27
|
+
puts "Missing argument: class. Please specify class=ClassName"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -3,29 +3,19 @@ $:.push File.expand_path("../lib", __FILE__)
|
|
3
3
|
require "simple_state_machine/version"
|
4
4
|
|
5
5
|
Gem::Specification.new do |s|
|
6
|
-
s.name
|
7
|
-
s.version
|
8
|
-
s.platform
|
9
|
-
|
10
|
-
s.
|
11
|
-
s.
|
12
|
-
s.
|
13
|
-
s.
|
14
|
-
s.
|
15
|
-
|
16
|
-
"
|
17
|
-
]
|
18
|
-
s.
|
19
|
-
s.
|
20
|
-
s.rdoc_options = ["--charset=UTF-8"]
|
21
|
-
s.require_paths = ["lib"]
|
22
|
-
s.rubygems_version = %q{1.3.7}
|
23
|
-
s.summary = %q{A simple DSL to decorate existing methods with logic that guards state transitions.}
|
24
|
-
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
25
|
-
s.add_development_dependency "rake"
|
26
|
-
s.add_development_dependency "ZenTest"
|
27
|
-
s.add_development_dependency "rspec"
|
28
|
-
s.add_development_dependency "activerecord", "~>2.3.5"
|
29
|
-
s.add_development_dependency "sqlite3-ruby"
|
6
|
+
s.name = %q{simple_state_machine}
|
7
|
+
s.version = SimpleStateMachine::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Marek de Heus", "Petrik de Heus"]
|
10
|
+
s.description = %q{A simple DSL to decorate existing methods with state transition guards.}
|
11
|
+
s.email = ["FIX@example.com"]
|
12
|
+
s.homepage = %q{http://github.com/mdh/ssm}
|
13
|
+
s.extra_rdoc_files = ["LICENSE","README.rdoc"]
|
14
|
+
s.files = `git ls-files`.split("\n")
|
15
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
16
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
17
|
+
s.require_paths = ["lib"]
|
18
|
+
s.summary = %q{A simple DSL to decorate existing methods with logic that guards state transitions.}
|
19
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
30
20
|
end
|
31
21
|
|
data/spec/.DS_Store
ADDED
Binary file
|
data/spec/active_record_spec.rb
CHANGED
@@ -1,213 +1,250 @@
|
|
1
|
-
require
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
t.column :activation_code, :string
|
21
|
-
t.column :created_at, :datetime
|
22
|
-
t.column :updated_at, :datetime
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
if defined? ActiveRecord
|
5
|
+
ActiveRecord::Base.logger = Logger.new "test.log"
|
6
|
+
ActiveRecord::Base.establish_connection('adapter' => RUBY_PLATFORM == 'java' ? 'jdbcsqlite3' : 'sqlite3', 'database' => ':memory:')
|
7
|
+
|
8
|
+
def setup_db
|
9
|
+
ActiveRecord::Schema.define(:version => 1) do
|
10
|
+
create_table :users do |t|
|
11
|
+
t.column :name, :string
|
12
|
+
t.column :state, :string
|
13
|
+
t.column :activation_code, :string
|
14
|
+
t.column :created_at, :datetime
|
15
|
+
t.column :updated_at, :datetime
|
16
|
+
end
|
17
|
+
create_table :tickets do |t|
|
18
|
+
t.column :ssm_state, :string
|
19
|
+
end
|
23
20
|
end
|
24
|
-
create_table :tickets do |t|
|
25
|
-
t.column :id, :integer
|
26
|
-
t.column :ssm_state, :string
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
def teardown_db
|
32
|
-
ActiveRecord::Base.connection.tables.each do |table|
|
33
|
-
ActiveRecord::Base.connection.drop_table(table)
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
class Ticket < ActiveRecord::Base
|
38
|
-
extend SimpleStateMachine::ActiveRecord
|
39
|
-
|
40
|
-
state_machine_definition.state_method = :ssm_state
|
41
|
-
|
42
|
-
def after_initialize
|
43
|
-
self.ssm_state ||= 'open'
|
44
|
-
end
|
45
|
-
|
46
|
-
event :close, :open => :closed
|
47
|
-
end
|
48
|
-
|
49
|
-
describe ActiveRecord do
|
50
|
-
|
51
|
-
before do
|
52
|
-
setup_db
|
53
|
-
end
|
54
|
-
|
55
|
-
after do
|
56
|
-
teardown_db
|
57
21
|
end
|
58
22
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
# TODO needs nesting/grouping, seems to have some duplication
|
64
|
-
|
65
|
-
describe "event_and_save" do
|
66
|
-
it "persists transitions" do
|
67
|
-
user = User.create!(:name => 'name')
|
68
|
-
user.invite_and_save.should == true
|
69
|
-
User.find(user.id).should be_invited
|
70
|
-
User.find(user.id).activation_code.should_not be_nil
|
71
|
-
end
|
72
|
-
|
73
|
-
it "persist transitions even when state is attr_protected" do
|
74
|
-
user_class = Class.new(User)
|
75
|
-
user_class.instance_eval { attr_protected :state }
|
76
|
-
user = user_class.create!(:name => 'name', :state => 'x')
|
77
|
-
user.should be_new
|
78
|
-
user.invite_and_save
|
79
|
-
user.reload.should be_invited
|
80
|
-
end
|
81
|
-
|
82
|
-
it "persists transitions when using send and a symbol" do
|
83
|
-
user = User.create!(:name => 'name')
|
84
|
-
user.send(:invite_and_save).should == true
|
85
|
-
User.find(user.id).should be_invited
|
86
|
-
User.find(user.id).activation_code.should_not be_nil
|
23
|
+
def teardown_db
|
24
|
+
ActiveRecord::Base.connection.tables.each do |table|
|
25
|
+
ActiveRecord::Base.connection.drop_table(table)
|
87
26
|
end
|
27
|
+
end
|
88
28
|
|
89
|
-
|
90
|
-
|
91
|
-
expect {
|
92
|
-
user.confirm_invitation_and_save 'abc'
|
93
|
-
}.to raise_error(SimpleStateMachine::IllegalStateTransitionError,
|
94
|
-
"You cannot 'confirm_invitation' when state is 'new'")
|
95
|
-
end
|
29
|
+
class Ticket < ActiveRecord::Base
|
30
|
+
extend SimpleStateMachine::ActiveRecord
|
96
31
|
|
97
|
-
|
98
|
-
user = User.new
|
99
|
-
user.should be_new
|
100
|
-
user.should_not be_valid
|
101
|
-
user.invite_and_save.should == false
|
102
|
-
user.should be_new
|
103
|
-
end
|
32
|
+
state_machine_definition.state_method = :ssm_state
|
104
33
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
user.should be_invited
|
109
|
-
user.confirm_invitation_and_save('x').should == false
|
110
|
-
user.should be_invited
|
111
|
-
user.errors.entries.should == [['activation_code', 'is invalid']]
|
34
|
+
after_initialize :after_initialize
|
35
|
+
def after_initialize
|
36
|
+
self.ssm_state ||= 'open'
|
112
37
|
end
|
113
38
|
|
39
|
+
event :close, :open => :closed
|
114
40
|
end
|
115
41
|
|
116
|
-
describe
|
42
|
+
describe ActiveRecord do
|
117
43
|
|
118
|
-
|
119
|
-
|
120
|
-
user.invite_and_save!.should == true
|
121
|
-
User.find(user.id).should be_invited
|
122
|
-
User.find(user.id).activation_code.should_not be_nil
|
44
|
+
before do
|
45
|
+
setup_db
|
123
46
|
end
|
124
47
|
|
125
|
-
|
126
|
-
|
127
|
-
user_class.instance_eval { attr_protected :state }
|
128
|
-
user = user_class.create!(:name => 'name', :state => 'x')
|
129
|
-
user.should be_new
|
130
|
-
user.invite_and_save!
|
131
|
-
user.reload.should be_invited
|
48
|
+
after do
|
49
|
+
teardown_db
|
132
50
|
end
|
133
51
|
|
134
|
-
it "
|
135
|
-
|
136
|
-
expect {
|
137
|
-
user.confirm_invitation_and_save! 'abc'
|
138
|
-
}.to raise_error(SimpleStateMachine::IllegalStateTransitionError,
|
139
|
-
"You cannot 'confirm_invitation' when state is 'new'")
|
52
|
+
it "has a default state" do
|
53
|
+
expect(User.new).to be_new
|
140
54
|
end
|
141
55
|
|
142
|
-
it "
|
143
|
-
user = User.new
|
144
|
-
user.should be_new
|
145
|
-
user.should_not be_valid
|
146
|
-
expect {
|
147
|
-
user.invite_and_save!
|
148
|
-
}.to raise_error(ActiveRecord::RecordInvalid,
|
149
|
-
"Validation failed: Name can't be blank")
|
150
|
-
user.should be_new
|
151
|
-
end
|
152
|
-
|
153
|
-
it "raises a RecordInvalid and keeps state if event adds errors" do
|
56
|
+
it "persists transitions when using send and a symbol" do
|
154
57
|
user = User.create!(:name => 'name')
|
155
|
-
user.invite_and_save
|
156
|
-
user.
|
157
|
-
expect
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
58
|
+
expect(user.send(:invite_and_save)).to eq(true)
|
59
|
+
expect(User.find(user.id)).to be_invited
|
60
|
+
expect(User.find(user.id).activation_code).not_to be_nil
|
61
|
+
end
|
62
|
+
|
63
|
+
# TODO needs nesting/grouping, seems to have some duplication
|
64
|
+
|
65
|
+
describe "event_and_save" do
|
66
|
+
it "persists transitions" do
|
67
|
+
user = User.create!(:name => 'name')
|
68
|
+
expect(user.invite_and_save).to eq(true)
|
69
|
+
expect(User.find(user.id)).to be_invited
|
70
|
+
expect(User.find(user.id).activation_code).not_to be_nil
|
71
|
+
end
|
72
|
+
|
73
|
+
it "persists transitions when using send and a symbol" do
|
74
|
+
user = User.create!(:name => 'name')
|
75
|
+
expect(user.send(:invite_and_save)).to eq(true)
|
76
|
+
expect(User.find(user.id)).to be_invited
|
77
|
+
expect(User.find(user.id).activation_code).not_to be_nil
|
78
|
+
end
|
79
|
+
|
80
|
+
it "raises an error if an invalid state_transition is called" do
|
81
|
+
user = User.create!(:name => 'name')
|
82
|
+
expect {
|
83
|
+
user.confirm_invitation_and_save 'abc'
|
84
|
+
}.to raise_error(SimpleStateMachine::IllegalStateTransitionError,
|
85
|
+
"You cannot 'confirm_invitation' when state is 'new'")
|
86
|
+
end
|
87
|
+
|
88
|
+
it "returns false and keeps state if record is invalid" do
|
89
|
+
user = User.new
|
90
|
+
expect(user).to be_new
|
91
|
+
expect(user).not_to be_valid
|
92
|
+
expect(user.invite_and_save).to eq(false)
|
93
|
+
expect(user).to be_new
|
94
|
+
end
|
95
|
+
|
96
|
+
it "returns false, keeps state and keeps errors if event adds errors" do
|
97
|
+
user = User.create!(:name => 'name')
|
98
|
+
user.invite_and_save!
|
99
|
+
expect(user).to be_invited
|
100
|
+
expect(user.confirm_invitation_and_save('x')).to eq(false)
|
101
|
+
expect(user).to be_invited
|
102
|
+
expect(Array(user.errors[:activation_code])).to eq(['is invalid'])
|
103
|
+
end
|
104
|
+
|
105
|
+
it "rollsback if an exception is raised" do
|
106
|
+
user_class = Class.new(User)
|
107
|
+
user_class.instance_eval do
|
108
|
+
define_method :invite_without_managed_state do
|
109
|
+
User.create!(:name => 'name2') #this shouldn't be persisted
|
110
|
+
User.create! #this should raise an error
|
111
|
+
end
|
112
|
+
end
|
113
|
+
expect(user_class.count).to eq(0)
|
114
|
+
user = user_class.create!(:name => 'name')
|
115
|
+
expect {
|
116
|
+
user.transaction { user.invite_and_save }
|
117
|
+
}.to raise_error(ActiveRecord::RecordInvalid,
|
118
|
+
"Validation failed: Name can't be blank")
|
119
|
+
expect(user_class.count).to eq(1)
|
120
|
+
expect(user_class.first.name).to eq('name')
|
121
|
+
expect(user_class.first).to be_new
|
122
|
+
end
|
123
|
+
|
124
|
+
it "raises an error if an invalid state_transition is called" do
|
125
|
+
user = User.create!(:name => 'name')
|
126
|
+
expect {
|
127
|
+
user.confirm_invitation_and_save! 'abc'
|
128
|
+
}.to raise_error(SimpleStateMachine::IllegalStateTransitionError,
|
129
|
+
"You cannot 'confirm_invitation' when state is 'new'")
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
describe "event_and_save!" do
|
134
|
+
|
135
|
+
it "persists transitions" do
|
136
|
+
user = User.create!(:name => 'name')
|
137
|
+
expect(user.invite_and_save!).to eq(true)
|
138
|
+
expect(User.find(user.id)).to be_invited
|
139
|
+
expect(User.find(user.id).activation_code).not_to be_nil
|
140
|
+
end
|
141
|
+
|
142
|
+
it "raises an error if an invalid state_transition is called" do
|
143
|
+
user = User.create!(:name => 'name')
|
144
|
+
expect {
|
145
|
+
user.confirm_invitation_and_save! 'abc'
|
146
|
+
}.to raise_error(SimpleStateMachine::IllegalStateTransitionError,
|
147
|
+
"You cannot 'confirm_invitation' when state is 'new'")
|
148
|
+
end
|
149
|
+
|
150
|
+
it "raises a RecordInvalid and keeps state if record is invalid" do
|
151
|
+
user = User.new
|
152
|
+
expect(user).to be_new
|
153
|
+
expect(user).not_to be_valid
|
154
|
+
expect {
|
155
|
+
user.invite_and_save!
|
156
|
+
}.to raise_error(ActiveRecord::RecordInvalid,
|
157
|
+
"Validation failed: Name can't be blank")
|
158
|
+
expect(user).to be_new
|
159
|
+
end
|
160
|
+
|
161
|
+
it "raises a RecordInvalid and keeps state if event adds errors" do
|
162
|
+
user = User.create!(:name => 'name')
|
163
|
+
user.invite_and_save!
|
164
|
+
expect(user).to be_invited
|
165
|
+
expect {
|
166
|
+
user.confirm_invitation_and_save!('x')
|
167
|
+
}.to raise_error(ActiveRecord::RecordInvalid,
|
168
|
+
"Validation failed: Activation code is invalid")
|
169
|
+
expect(user).to be_invited
|
170
|
+
end
|
171
|
+
|
172
|
+
it "rollsback if an exception is raised" do
|
173
|
+
user_class = Class.new(User)
|
174
|
+
user_class.instance_eval do
|
175
|
+
define_method :invite_without_managed_state do
|
176
|
+
User.create!(:name => 'name2') #this shouldn't be persisted
|
177
|
+
User.create! #this should raise an error
|
178
|
+
end
|
179
|
+
end
|
180
|
+
expect(user_class.count).to eq(0)
|
181
|
+
user = user_class.create!(:name => 'name')
|
182
|
+
expect {
|
183
|
+
user.transaction { user.invite_and_save! }
|
184
|
+
}.to raise_error(ActiveRecord::RecordInvalid,
|
185
|
+
"Validation failed: Name can't be blank")
|
186
|
+
expect(user_class.count).to eq(1)
|
187
|
+
expect(user_class.first.name).to eq('name')
|
188
|
+
expect(user_class.first).to be_new
|
189
|
+
end
|
190
|
+
|
191
|
+
end
|
192
|
+
|
193
|
+
describe "event" do
|
194
|
+
it "persists transitions" do
|
195
|
+
user = User.create!(:name => 'name')
|
196
|
+
expect(user.invite).to eq(true)
|
197
|
+
expect(User.find(user.id)).to be_invited
|
198
|
+
expect(User.find(user.id).activation_code).not_to be_nil
|
199
|
+
end
|
200
|
+
|
201
|
+
it "returns false, keeps state and keeps errors if event adds errors" do
|
202
|
+
user = User.create!(:name => 'name')
|
203
|
+
user.invite_and_save!
|
204
|
+
expect(user).to be_invited
|
205
|
+
expect(user.confirm_invitation('x')).to eq(false)
|
206
|
+
expect(user).to be_invited
|
207
|
+
expect(Array(user.errors[:activation_code])).to eq(['is invalid'])
|
208
|
+
end
|
167
209
|
|
168
|
-
it "raises an error" do
|
169
|
-
user = User.create!(:name => 'name')
|
170
|
-
expect { user.invite }.to raise_error(RuntimeError, "You cannot call invite. Use invite! instead")
|
171
210
|
end
|
172
211
|
|
173
|
-
|
212
|
+
describe "event!" do
|
174
213
|
|
175
|
-
|
214
|
+
it "persists transitions" do
|
215
|
+
user = User.create!(:name => 'name')
|
216
|
+
expect(user.invite!).to eq(true)
|
217
|
+
expect(User.find(user.id)).to be_invited
|
218
|
+
expect(User.find(user.id).activation_code).not_to be_nil
|
219
|
+
end
|
176
220
|
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
221
|
+
it "raises a RecordInvalid and keeps state if record is invalid" do
|
222
|
+
user = User.new
|
223
|
+
expect(user).to be_new
|
224
|
+
expect(user).not_to be_valid
|
225
|
+
expect { user.invite! }.to raise_error(ActiveRecord::RecordInvalid, "Validation failed: Name can't be blank")
|
226
|
+
expect(user).to be_new
|
227
|
+
end
|
183
228
|
|
184
|
-
it "raises a RecordInvalid and keeps state if record is invalid" do
|
185
|
-
user = User.new
|
186
|
-
user.should be_new
|
187
|
-
user.should_not be_valid
|
188
|
-
expect { user.invite! }.to raise_error(ActiveRecord::RecordInvalid, "Validation failed: Name can't be blank")
|
189
|
-
user.should be_new
|
190
229
|
end
|
191
230
|
|
192
|
-
|
231
|
+
describe 'custom state method' do
|
193
232
|
|
194
|
-
|
233
|
+
it "persists transitions" do
|
234
|
+
ticket = Ticket.create!
|
235
|
+
expect(ticket.ssm_state).to eq('open')
|
236
|
+
expect(ticket.close!).to eq(true)
|
237
|
+
expect(ticket.ssm_state).to eq('closed')
|
238
|
+
end
|
195
239
|
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
240
|
+
it "persists transitions with !" do
|
241
|
+
ticket = Ticket.create!
|
242
|
+
expect(ticket.ssm_state).to eq('open')
|
243
|
+
ticket.close!
|
244
|
+
expect(ticket.ssm_state).to eq('closed')
|
245
|
+
end
|
202
246
|
|
203
|
-
it "persists transitions with !" do
|
204
|
-
ticket = Ticket.create!
|
205
|
-
ticket.should be_open
|
206
|
-
ticket.close!
|
207
|
-
ticket.should be_closed
|
208
247
|
end
|
209
248
|
|
210
249
|
end
|
211
|
-
|
212
250
|
end
|
213
|
-
|