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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: eb317c62d61044bf3d15ec90a14682f330f4ea86
4
- data.tar.gz: df1622719c738cfdafd5925042a0ba8c0c4d612c
3
+ metadata.gz: 697717ea3c8a4b388c0b43a8777d9c1a18e147c1
4
+ data.tar.gz: 14051a4eacb2979013b8ff8e88698e9be6cff436
5
5
  SHA512:
6
- metadata.gz: ce715eb5662eb4758d9d637d0575d5cec80859b859400699f783088ec64cdad1a2a661cad2d816e8e14fc03f7b91ec4e4305b64d973eaad959c2112d0ea573b3
7
- data.tar.gz: e649cc276449930c9e8615707cf993572a090635e3cdaea838d82c77f825a880243c644a368a76522b7da2c9debb2656d978e71e92538ef52240b74781fe24a3
6
+ metadata.gz: 8fef4baf05db94227a2d3d9b2cbce8b7de0b0cc4eebacc6c3c04d879562b823c0903f8044ed36c2d0ba30443600ce7e04b1d109aa0fd4b501a85be51d2e640cc
7
+ data.tar.gz: 34deb50e48efc779d09ddff16b61afa90c7a36c263af58f8af8f1995ff8db1ca01a610e00182b9b11eed8d3a665b768c2d4c86dfeecd336e73e47efb90da53f0
data/.travis.yml CHANGED
@@ -1,4 +1,5 @@
1
1
  rvm:
2
+ - 2.1.0
2
3
  - 2.0.0
3
4
  - 1.9.3
4
5
  services: mongodb
data/CHANGELOG.md CHANGED
@@ -1,4 +1,9 @@
1
- ## v 0.6.1 21 May 2014
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/backend_developer).
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
- unless transition_class.serialized_attributes.include?("metadata")
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
- transition = transitions_for_parent.build(to_state: to,
21
- sort_key: next_sort_key,
22
- metadata: metadata)
23
-
24
- ::ActiveRecord::Base.transaction do
25
- @observer.execute(:before, from, to, transition)
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
- @observer.execute(:after_commit, from, to, transition)
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
@@ -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
@@ -143,8 +143,8 @@ module Statesman
143
143
  })
144
144
  @object = object
145
145
  @transition_class = options[:transition_class]
146
- @storage_adapter = Statesman.storage_adapter.new(
147
- @transition_class, object, self)
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
@@ -1,3 +1,3 @@
1
1
  module Statesman
2
- VERSION = "0.6.1"
2
+ VERSION = "0.7.0"
3
3
  end
@@ -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 { MyActiveRecordModelTransition.stub(serialized_attributes: {}) }
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 if metadata is not serialized" do
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.6.1
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-05-21 00:00:00.000000000 Z
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.2.2
233
+ rubygems_version: 2.0.14
233
234
  signing_key:
234
235
  specification_version: 4
235
236
  summary: A statesmanlike state machine library