statesman 1.2.0 → 1.2.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 +4 -4
- data/.travis.yml +1 -0
- data/CHANGELOG.md +4 -0
- data/Gemfile +5 -0
- data/README.md +49 -105
- data/lib/statesman/adapters/active_record.rb +4 -2
- data/lib/statesman/version.rb +1 -1
- data/spec/statesman/adapters/active_record_spec.rb +28 -0
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dc520c4907fb1573b7682bd1db4affe787b1d036
|
4
|
+
data.tar.gz: 796e3a0482fc5814b9ae9da4ec870dc97d43b851
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3ae6ba2af2f37b4df09d025d2bdfce5849aba5ba3e23a3d7c341061db4ad8002547177302a155b29c2770bc58d5b0988c1df2854e186a36d0236f9d5f0ca96e9
|
7
|
+
data.tar.gz: 57d5ab47d228ae72de01cff8e9b03db86bdb755bb70b8231a5f7639d7d45320fc647587061c76053353c4d56004a3dbefa8a52045e4bbd6e6175bc58fbc69cc3
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
data/Gemfile
CHANGED
@@ -3,3 +3,8 @@ source 'https://rubygems.org'
|
|
3
3
|
gemspec
|
4
4
|
|
5
5
|
gem "rails", "~> #{ENV["RAILS_VERSION"]}" if ENV["RAILS_VERSION"]
|
6
|
+
|
7
|
+
# test/unit is no longer bundled with Ruby 2.2, but required by Rails
|
8
|
+
if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.2.0")
|
9
|
+
gem "test-unit", "~> 3.0"
|
10
|
+
end
|
data/README.md
CHANGED
@@ -1,26 +1,34 @@
|
|
1
1
|

|
2
2
|
|
3
|
-
A statesmanlike state machine library for Ruby 1.9.3 and
|
3
|
+
A statesmanlike state machine library for Ruby 1.9.3 and up.
|
4
4
|
|
5
5
|
[](http://badge.fury.io/rb/statesman)
|
6
6
|
[](https://travis-ci.org/gocardless/statesman)
|
7
7
|
[](https://codeclimate.com/github/gocardless/statesman)
|
8
8
|
[](https://gitter.im/gocardless/statesman?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
10
|
+
Statesman is an opinionated state machine library designed to provide a robust
|
11
|
+
audit trail and data integrity. It decouples the state machine logic from the
|
12
|
+
underlying model and allows for easy composition with one or more model classes.
|
13
|
+
|
14
|
+
As such, the design of statesman is a little different from other state machine
|
15
|
+
libraries:
|
16
|
+
- State behaviour is defined in a separate, "state machine" class, rather than
|
17
|
+
added directly onto a model. State machines are then instantiated with the model
|
18
|
+
to which they should apply.
|
19
|
+
- State transitions are also modelled as a class, which can optionally be
|
20
|
+
persisted to the database for a full audit history. This audit history can
|
21
|
+
include JSON metadata set during a transition.
|
22
|
+
- Database indicies are used to offer database-level transaction duplication
|
23
|
+
protection.
|
20
24
|
|
21
25
|
## TL;DR Usage
|
22
26
|
|
23
27
|
```ruby
|
28
|
+
|
29
|
+
#######################
|
30
|
+
# State Machine Class #
|
31
|
+
#######################
|
24
32
|
class OrderStateMachine
|
25
33
|
include Statesman::Machine
|
26
34
|
|
@@ -54,6 +62,9 @@ class OrderStateMachine
|
|
54
62
|
end
|
55
63
|
end
|
56
64
|
|
65
|
+
##############
|
66
|
+
# Your Model #
|
67
|
+
##############
|
57
68
|
class Order < ActiveRecord::Base
|
58
69
|
include Statesman::Adapters::ActiveRecordQueries
|
59
70
|
|
@@ -74,96 +85,26 @@ class Order < ActiveRecord::Base
|
|
74
85
|
end
|
75
86
|
end
|
76
87
|
|
88
|
+
####################
|
89
|
+
# Transition Model #
|
90
|
+
####################
|
77
91
|
class OrderTransition < ActiveRecord::Base
|
78
92
|
include Statesman::Adapters::ActiveRecordTransition
|
79
93
|
|
80
94
|
belongs_to :order, inverse_of: :order_transitions
|
81
95
|
end
|
82
96
|
|
83
|
-
|
84
|
-
#
|
85
|
-
|
86
|
-
Order.first.state_machine.
|
87
|
-
# => ["checking_out", "cancelled"]
|
88
|
-
|
89
|
-
Order.first.state_machine.
|
90
|
-
# => true/
|
91
|
-
|
92
|
-
Order.first.state_machine.transition_to(:cancelled, optional: :metadata)
|
93
|
-
# => true/false
|
97
|
+
########################
|
98
|
+
# Example method calls #
|
99
|
+
########################
|
100
|
+
Order.first.state_machine.current_state # => "pending"
|
101
|
+
Order.first.state_machine.allowed_transitions # => ["checking_out", "cancelled"]
|
102
|
+
Order.first.state_machine.can_transition_to?(:cancelled) # => true/false
|
103
|
+
Order.first.state_machine.transition_to(:cancelled, optional: :metadata) # => true/false
|
104
|
+
Order.first.state_machine.transition_to!(:cancelled) # => true/exception
|
94
105
|
|
95
|
-
Order.in_state(:cancelled)
|
96
|
-
# => [#<Order id: "123">]
|
97
|
-
|
98
|
-
Order.not_in_state(:checking_out)
|
99
|
-
# => [#<Order id: "123">]
|
100
|
-
|
101
|
-
Order.first.state_machine.transition_to!(:cancelled)
|
102
|
-
# => true/exception
|
103
|
-
```
|
104
|
-
|
105
|
-
## Events
|
106
|
-
|
107
|
-
```ruby
|
108
|
-
class TaskStateMachine
|
109
|
-
include Statesman::Machine
|
110
|
-
|
111
|
-
state :unstarted, initial: true
|
112
|
-
state :started
|
113
|
-
state :finished
|
114
|
-
state :delivered
|
115
|
-
state :accepted
|
116
|
-
state :rejected
|
117
|
-
|
118
|
-
event :start do
|
119
|
-
transition from: :unstarted, to: :started
|
120
|
-
end
|
121
|
-
|
122
|
-
event :finish do
|
123
|
-
transition from: :started, to: :finished
|
124
|
-
end
|
125
|
-
|
126
|
-
event :deliver do
|
127
|
-
transition from: :finished, to: :delivered
|
128
|
-
transition from: :started, to: :delivered
|
129
|
-
end
|
130
|
-
|
131
|
-
event :accept do
|
132
|
-
transition from: :delivered, to: :accepted
|
133
|
-
end
|
134
|
-
|
135
|
-
event :rejected do
|
136
|
-
transition from: :delivered, to: :rejected
|
137
|
-
end
|
138
|
-
|
139
|
-
event :restart do
|
140
|
-
transition from: :rejected, to: :started
|
141
|
-
end
|
142
|
-
|
143
|
-
end
|
144
|
-
|
145
|
-
class Task < ActiveRecord::Base
|
146
|
-
delegate :current_state, :trigger!, :available_events, to: :state_machine
|
147
|
-
|
148
|
-
def state_machine
|
149
|
-
@state_machine ||= TaskStateMachine.new(self)
|
150
|
-
end
|
151
|
-
|
152
|
-
end
|
153
|
-
|
154
|
-
task = Task.new
|
155
|
-
|
156
|
-
task.current_state
|
157
|
-
# => "unstarted"
|
158
|
-
|
159
|
-
task.trigger!(:start)
|
160
|
-
# => true/exception
|
161
|
-
|
162
|
-
task.current_state
|
163
|
-
# => "started"
|
164
|
-
|
165
|
-
task.available_events
|
166
|
-
# => [:finish, :deliver]
|
106
|
+
Order.in_state(:cancelled) # => [#<Order id: "123">]
|
107
|
+
Order.not_in_state(:checking_out) # => [#<Order id: "123">]
|
167
108
|
|
168
109
|
```
|
169
110
|
|
@@ -222,14 +163,6 @@ It is also possible to use the PostgreSQL JSON column if you are using Rails 4.
|
|
222
163
|
* Remove `include Statesman::Adapters::ActiveRecordTransition` statement from your
|
223
164
|
transition model
|
224
165
|
|
225
|
-
#### Creating transitions without using `#transition_to` with ActiveRecord
|
226
|
-
|
227
|
-
By default, Statesman will include a `most_recent` column on the transitions
|
228
|
-
table, and update its value each time `#transition_to` is called. If you create
|
229
|
-
transitions manually (for example to backfill for a new state) you will need to
|
230
|
-
set the `most_recent` attribute manually.
|
231
|
-
|
232
|
-
|
233
166
|
## Configuration
|
234
167
|
|
235
168
|
#### `storage_adapter`
|
@@ -366,16 +299,17 @@ end
|
|
366
299
|
```
|
367
300
|
|
368
301
|
#### `Model.in_state(:state_1, :state_2, etc)`
|
369
|
-
Returns all models currently in any of the supplied states.
|
302
|
+
Returns all models currently in any of the supplied states.
|
370
303
|
|
371
304
|
#### `Model.not_in_state(:state_1, :state_2, etc)`
|
372
|
-
Returns all models not currently in any of the supplied states.
|
305
|
+
Returns all models not currently in any of the supplied states.
|
373
306
|
|
374
307
|
## Frequently Asked Questions
|
375
308
|
|
376
309
|
#### Storing the state on the model object
|
377
310
|
|
378
|
-
If you wish to store the model state on the model directly, you can keep it up
|
311
|
+
If you wish to store the model state on the model directly, you can keep it up
|
312
|
+
to date using an `after_transition` hook:
|
379
313
|
|
380
314
|
```ruby
|
381
315
|
after_transition do |model, transition|
|
@@ -394,6 +328,16 @@ Given a field `foo` that was stored in the metadata, you can access it like so:
|
|
394
328
|
model_instance.last_transition.metadata["foo"]
|
395
329
|
```
|
396
330
|
|
331
|
+
#### Upgrading from 1.1 to 1.2
|
332
|
+
|
333
|
+
Statesman 1.2.0 introduced a new `most_recent` column on the transition model,
|
334
|
+
which is used to speed up queries.
|
335
|
+
|
336
|
+
The change is entirely backwards compatible, but if you'd like the performance
|
337
|
+
improvements just follow
|
338
|
+
[this guide](https://github.com/gocardless/statesman/wiki/Adding-a-%60most_recent%60-column-to-an-existing-model)
|
339
|
+
to add a `most_recent` column to an existing model.
|
340
|
+
|
397
341
|
## Testing Statesman Implementations
|
398
342
|
|
399
343
|
This answer was abstracted from [this issue](https://github.com/gocardless/statesman/issues/77).
|
@@ -6,13 +6,15 @@ module Statesman
|
|
6
6
|
attr_reader :transition_class
|
7
7
|
attr_reader :parent_model
|
8
8
|
|
9
|
+
JSON_COLUMN_TYPES = %w(json jsonb).freeze
|
10
|
+
|
9
11
|
def initialize(transition_class, parent_model, observer)
|
10
12
|
serialized = serialized?(transition_class)
|
11
13
|
column_type = transition_class.columns_hash['metadata'].sql_type
|
12
|
-
if !serialized && column_type
|
14
|
+
if !serialized && !JSON_COLUMN_TYPES.include?(column_type)
|
13
15
|
raise UnserializedMetadataError,
|
14
16
|
"#{transition_class.name}#metadata is not serialized"
|
15
|
-
elsif serialized && column_type
|
17
|
+
elsif serialized && JSON_COLUMN_TYPES.include?(column_type)
|
16
18
|
raise IncompatibleSerializationError,
|
17
19
|
"#{transition_class.name}#metadata column type cannot be json
|
18
20
|
and serialized simultaneously"
|
data/lib/statesman/version.rb
CHANGED
@@ -64,6 +64,34 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
|
|
64
64
|
end.to raise_exception(Statesman::IncompatibleSerializationError)
|
65
65
|
end
|
66
66
|
end
|
67
|
+
|
68
|
+
context "with serialized metadata and jsonb column type" do
|
69
|
+
before do
|
70
|
+
metadata_column = double
|
71
|
+
allow(metadata_column).to receive_messages(sql_type: 'jsonb')
|
72
|
+
allow(MyActiveRecordModelTransition).to receive_messages(columns_hash:
|
73
|
+
{ 'metadata' => metadata_column })
|
74
|
+
if ::ActiveRecord.respond_to?(:gem_version) &&
|
75
|
+
::ActiveRecord.gem_version >= Gem::Version.new('4.2.0.a')
|
76
|
+
serialized_type = ::ActiveRecord::Type::Serialized.new(
|
77
|
+
'', ::ActiveRecord::Coders::JSON
|
78
|
+
)
|
79
|
+
expect(metadata_column).
|
80
|
+
to receive(:cast_type).
|
81
|
+
and_return(serialized_type)
|
82
|
+
else
|
83
|
+
expect(MyActiveRecordModelTransition).
|
84
|
+
to receive_messages(serialized_attributes: { 'metadata' => '' })
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
it "raises an exception" do
|
89
|
+
expect do
|
90
|
+
described_class.new(MyActiveRecordModelTransition,
|
91
|
+
MyActiveRecordModel, observer)
|
92
|
+
end.to raise_exception(Statesman::IncompatibleSerializationError)
|
93
|
+
end
|
94
|
+
end
|
67
95
|
end
|
68
96
|
|
69
97
|
describe "#create" do
|
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: 1.2.
|
4
|
+
version: 1.2.1
|
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: 2015-03-
|
12
|
+
date: 2015-03-24 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -283,7 +283,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
283
283
|
version: '0'
|
284
284
|
requirements: []
|
285
285
|
rubyforge_project:
|
286
|
-
rubygems_version: 2.4.
|
286
|
+
rubygems_version: 2.4.1
|
287
287
|
signing_key:
|
288
288
|
specification_version: 4
|
289
289
|
summary: A statesmanlike state machine library
|