statesman 0.6.1 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +1 -0
- data/CHANGELOG.md +6 -1
- data/CONTRIBUTING.md +28 -0
- data/README.md +3 -1
- data/lib/statesman/adapters/active_record.rb +31 -14
- data/lib/statesman/adapters/mongoid.rb +2 -1
- data/lib/statesman/exceptions.rb +2 -0
- data/lib/statesman/machine.rb +10 -2
- data/lib/statesman/version.rb +1 -1
- data/spec/statesman/adapters/active_record_spec.rb +65 -3
- data/spec/statesman/machine_spec.rb +7 -0
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 697717ea3c8a4b388c0b43a8777d9c1a18e147c1
|
4
|
+
data.tar.gz: 14051a4eacb2979013b8ff8e88698e9be6cff436
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8fef4baf05db94227a2d3d9b2cbce8b7de0b0cc4eebacc6c3c04d879562b823c0903f8044ed36c2d0ba30443600ce7e04b1d109aa0fd4b501a85be51d2e640cc
|
7
|
+
data.tar.gz: 34deb50e48efc779d09ddff16b61afa90c7a36c263af58f8af8f1995ff8db1ca01a610e00182b9b11eed8d3a665b768c2d4c86dfeecd336e73e47efb90da53f0
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,9 @@
|
|
1
|
-
##
|
1
|
+
## v0.7.0 25 June 2014
|
2
|
+
*Additions*
|
3
|
+
|
4
|
+
- `Adapters::ActiveRecord` now handles `ActiveRecord::RecordNotUnique` errors explicitly and re-raises with a `Statesman::TransitionConflictError` if it is due to duplicate sort_keys (patch by [@greysteil](https://github.com/greysteil))
|
5
|
+
|
6
|
+
## v0.6.1 21 May 2014
|
2
7
|
*Fixes*
|
3
8
|
- Fixes an issue where the wrong transition was passed to after_transition callbacks for the second and subsequent transition of a given state machine (patch by [@alan](https://github.com/alan))
|
4
9
|
|
data/CONTRIBUTING.md
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
Thanks for taking an interest in contributing to Statesman, here are a few
|
2
|
+
ways you can help make this project better!
|
3
|
+
|
4
|
+
# Contribute.md
|
5
|
+
|
6
|
+
## Team members
|
7
|
+
|
8
|
+
- [Andy Appleton](https://twitter.com/appltn)
|
9
|
+
- [Harry Marr](https://twitter.com/harrymarr)
|
10
|
+
|
11
|
+
## Contributing
|
12
|
+
|
13
|
+
- Generally we welcome new features but please first open an issue where we
|
14
|
+
can discuss whether it fits with our vision for the project.
|
15
|
+
- Any new feature or bug fix needs an accompanying test case.
|
16
|
+
- No need to add to the changelog, we will take care of updating it as we make
|
17
|
+
releases.
|
18
|
+
|
19
|
+
## Style
|
20
|
+
|
21
|
+
We use [Rubocop](https://github.com/bbatsov/rubocop) to help maintain a
|
22
|
+
consistent code style across the project. Please check that your pull
|
23
|
+
request passes by running `rubocop`.
|
24
|
+
|
25
|
+
## Documentation
|
26
|
+
|
27
|
+
Please add a section to the readme for any new feature additions or behaviour
|
28
|
+
changes.
|
data/README.md
CHANGED
@@ -150,6 +150,8 @@ Statesman defaults to storing transitions in memory. If you're using rails, you
|
|
150
150
|
can instead configure it to persist transitions to the database by using the
|
151
151
|
ActiveRecord or Mongoid adapter.
|
152
152
|
|
153
|
+
Statesman will fallback to memory unless you specify a transition_class when instantiating your state machine. This allows you to only persist transitions on certain state machines in your app.
|
154
|
+
|
153
155
|
|
154
156
|
## Class methods
|
155
157
|
|
@@ -260,4 +262,4 @@ Returns all models not currently in any of the supplied states.
|
|
260
262
|
|
261
263
|
---
|
262
264
|
|
263
|
-
GoCardless ♥ open source. If you do too, come [join us](https://gocardless.com/jobs
|
265
|
+
GoCardless ♥ open source. If you do too, come [join us](https://gocardless.com/jobs#software-engineer).
|
@@ -7,9 +7,15 @@ module Statesman
|
|
7
7
|
attr_reader :parent_model
|
8
8
|
|
9
9
|
def initialize(transition_class, parent_model, observer)
|
10
|
-
|
10
|
+
serialized = transition_class.serialized_attributes.include?("metadata")
|
11
|
+
column_type = transition_class.columns_hash['metadata'].sql_type
|
12
|
+
if !serialized && column_type != 'json'
|
11
13
|
raise UnserializedMetadataError,
|
12
14
|
"#{transition_class.name}#metadata is not serialized"
|
15
|
+
elsif serialized && column_type == 'json'
|
16
|
+
raise IncompatibleSerializationError,
|
17
|
+
"#{transition_class.name}#metadata column type cannot be json
|
18
|
+
and serialized simultaneously"
|
13
19
|
end
|
14
20
|
@transition_class = transition_class
|
15
21
|
@parent_model = parent_model
|
@@ -17,20 +23,15 @@ module Statesman
|
|
17
23
|
end
|
18
24
|
|
19
25
|
def create(from, to, metadata = {})
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
transition.save!
|
27
|
-
@last_transition = transition
|
28
|
-
@observer.execute(:after, from, to, transition)
|
29
|
-
@last_transition = nil
|
26
|
+
create_transition(from, to, metadata)
|
27
|
+
rescue ::ActiveRecord::RecordNotUnique => e
|
28
|
+
if e.message.include?('sort_key') &&
|
29
|
+
e.message.include?(@transition_class.table_name)
|
30
|
+
raise TransitionConflictError, e.message
|
31
|
+
else raise
|
30
32
|
end
|
31
|
-
|
32
|
-
|
33
|
-
transition
|
33
|
+
ensure
|
34
|
+
@last_transition = nil
|
34
35
|
end
|
35
36
|
|
36
37
|
def history
|
@@ -47,6 +48,22 @@ module Statesman
|
|
47
48
|
|
48
49
|
private
|
49
50
|
|
51
|
+
def create_transition(from, to, metadata)
|
52
|
+
transition = transitions_for_parent.build(to_state: to,
|
53
|
+
sort_key: next_sort_key,
|
54
|
+
metadata: metadata)
|
55
|
+
|
56
|
+
::ActiveRecord::Base.transaction do
|
57
|
+
@observer.execute(:before, from, to, transition)
|
58
|
+
transition.save!
|
59
|
+
@last_transition = transition
|
60
|
+
@observer.execute(:after, from, to, transition)
|
61
|
+
end
|
62
|
+
@observer.execute(:after_commit, from, to, transition)
|
63
|
+
|
64
|
+
transition
|
65
|
+
end
|
66
|
+
|
50
67
|
def transitions_for_parent
|
51
68
|
@parent_model.send(@transition_class.table_name)
|
52
69
|
end
|
@@ -25,8 +25,9 @@ module Statesman
|
|
25
25
|
@last_transition = transition
|
26
26
|
@observer.execute(:after, from, to, transition)
|
27
27
|
@observer.execute(:after_commit, from, to, transition)
|
28
|
-
@last_transition = nil
|
29
28
|
transition
|
29
|
+
ensure
|
30
|
+
@last_transition = nil
|
30
31
|
end
|
31
32
|
|
32
33
|
def history
|
data/lib/statesman/exceptions.rb
CHANGED
@@ -5,4 +5,6 @@ module Statesman
|
|
5
5
|
class GuardFailedError < StandardError; end
|
6
6
|
class TransitionFailedError < StandardError; end
|
7
7
|
class UnserializedMetadataError < StandardError; end
|
8
|
+
class IncompatibleSerializationError < StandardError; end
|
9
|
+
class TransitionConflictError < StandardError; end
|
8
10
|
end
|
data/lib/statesman/machine.rb
CHANGED
@@ -143,8 +143,8 @@ module Statesman
|
|
143
143
|
})
|
144
144
|
@object = object
|
145
145
|
@transition_class = options[:transition_class]
|
146
|
-
@storage_adapter =
|
147
|
-
|
146
|
+
@storage_adapter = adapter_class(@transition_class)
|
147
|
+
.new(@transition_class, object, self)
|
148
148
|
send(:after_initialize) if respond_to? :after_initialize
|
149
149
|
end
|
150
150
|
|
@@ -202,6 +202,14 @@ module Statesman
|
|
202
202
|
|
203
203
|
private
|
204
204
|
|
205
|
+
def adapter_class(transition_class)
|
206
|
+
if transition_class == Statesman::Adapters::MemoryTransition
|
207
|
+
Adapters::Memory
|
208
|
+
else
|
209
|
+
Statesman.storage_adapter
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
205
213
|
def successors_for(from)
|
206
214
|
self.class.successors[from] || []
|
207
215
|
end
|
data/lib/statesman/version.rb
CHANGED
@@ -18,16 +18,78 @@ describe Statesman::Adapters::ActiveRecord do
|
|
18
18
|
it_behaves_like "an adapter", described_class, MyActiveRecordModelTransition
|
19
19
|
|
20
20
|
describe "#initialize" do
|
21
|
-
context "with unserialized metadata" do
|
22
|
-
before
|
21
|
+
context "with unserialized metadata and non json column type" do
|
22
|
+
before do
|
23
|
+
metadata_column = double
|
24
|
+
metadata_column.stub(sql_type: '')
|
25
|
+
MyActiveRecordModelTransition.stub(columns_hash:
|
26
|
+
{ 'metadata' => metadata_column })
|
27
|
+
MyActiveRecordModelTransition.stub(serialized_attributes: {})
|
28
|
+
end
|
23
29
|
|
24
|
-
it "raises an exception
|
30
|
+
it "raises an exception" do
|
25
31
|
expect do
|
26
32
|
described_class.new(MyActiveRecordModelTransition,
|
27
33
|
MyActiveRecordModel, observer)
|
28
34
|
end.to raise_exception(Statesman::UnserializedMetadataError)
|
29
35
|
end
|
30
36
|
end
|
37
|
+
|
38
|
+
context "with serialized metadata and json column type" do
|
39
|
+
before do
|
40
|
+
metadata_column = double
|
41
|
+
metadata_column.stub(sql_type: 'json')
|
42
|
+
MyActiveRecordModelTransition.stub(columns_hash:
|
43
|
+
{ 'metadata' => metadata_column })
|
44
|
+
MyActiveRecordModelTransition.stub(serialized_attributes:
|
45
|
+
{ 'metadata' => '' })
|
46
|
+
end
|
47
|
+
|
48
|
+
it "raises an exception" do
|
49
|
+
expect do
|
50
|
+
described_class.new(MyActiveRecordModelTransition,
|
51
|
+
MyActiveRecordModel, observer)
|
52
|
+
end.to raise_exception(Statesman::IncompatibleSerializationError)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "#create" do
|
58
|
+
let(:adapter) do
|
59
|
+
described_class.new(MyActiveRecordModelTransition, model, observer)
|
60
|
+
end
|
61
|
+
let(:from) { :x }
|
62
|
+
let(:to) { :y }
|
63
|
+
let(:create) { adapter.create(from, to) }
|
64
|
+
subject { -> { create } }
|
65
|
+
|
66
|
+
context "when there is a race" do
|
67
|
+
it "raises a TransitionConflictError" do
|
68
|
+
adapter2 = adapter.dup
|
69
|
+
adapter2.create(:x, :y)
|
70
|
+
adapter.last
|
71
|
+
adapter2.create(:y, :z)
|
72
|
+
expect { adapter.create(:y, :z) }
|
73
|
+
.to raise_exception(Statesman::TransitionConflictError)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context "when other exceptions occur" do
|
78
|
+
before do
|
79
|
+
allow_any_instance_of(MyActiveRecordModelTransition)
|
80
|
+
.to receive(:save!).and_raise(error)
|
81
|
+
end
|
82
|
+
|
83
|
+
context "ActiveRecord::RecordNotUnique unrelated to this transition" do
|
84
|
+
let(:error) { ActiveRecord::RecordNotUnique.new("unrelated", nil) }
|
85
|
+
it { should raise_exception(ActiveRecord::RecordNotUnique) }
|
86
|
+
end
|
87
|
+
|
88
|
+
context "other errors" do
|
89
|
+
let(:error) { StandardError }
|
90
|
+
it { should raise_exception(StandardError) }
|
91
|
+
end
|
92
|
+
end
|
31
93
|
end
|
32
94
|
|
33
95
|
describe "#last" do
|
@@ -203,6 +203,13 @@ describe Statesman::Machine do
|
|
203
203
|
.with(my_transition_class, my_model, anything)
|
204
204
|
machine.new(my_model, transition_class: my_transition_class)
|
205
205
|
end
|
206
|
+
|
207
|
+
it "falls back to Memory without transaction_class" do
|
208
|
+
Statesman.stub(:storage_adapter).and_return(Class.new)
|
209
|
+
Statesman::Adapters::Memory.should_receive(:new).once
|
210
|
+
.with(Statesman::Adapters::MemoryTransition, my_model, anything)
|
211
|
+
machine.new(my_model)
|
212
|
+
end
|
206
213
|
end
|
207
214
|
end
|
208
215
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: statesman
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Harry Marr
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2014-
|
12
|
+
date: 2014-07-25 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -162,6 +162,7 @@ files:
|
|
162
162
|
- .rubocop.yml
|
163
163
|
- .travis.yml
|
164
164
|
- CHANGELOG.md
|
165
|
+
- CONTRIBUTING.md
|
165
166
|
- Gemfile
|
166
167
|
- Guardfile
|
167
168
|
- LICENSE.txt
|
@@ -229,7 +230,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
229
230
|
version: '0'
|
230
231
|
requirements: []
|
231
232
|
rubyforge_project:
|
232
|
-
rubygems_version: 2.
|
233
|
+
rubygems_version: 2.0.14
|
233
234
|
signing_key:
|
234
235
|
specification_version: 4
|
235
236
|
summary: A statesmanlike state machine library
|