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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b7adec92ffe517ecf4227b1190dcf1289abce8fe
4
- data.tar.gz: 2f5876f6d294df4f2d52272c473d5c4521884d1d
3
+ metadata.gz: dc520c4907fb1573b7682bd1db4affe787b1d036
4
+ data.tar.gz: 796e3a0482fc5814b9ae9da4ec870dc97d43b851
5
5
  SHA512:
6
- metadata.gz: 9e9be2c9b771cbd4f2d19b7de27854f047659c33deb6907af3b38bb8130feda521f9faf22ffe7560dc01fed63390ab569aae6b8fe3469e1d4159efba430ba428
7
- data.tar.gz: e4e509269a3fd7adcfc3cfa15be27cef5d5e36e8e04fea8acf3c1fb6cef213ed3d91174c0efee5985eb691ec15ab59d105579769e651b4e1ca1d6546eb2cdc99
6
+ metadata.gz: 3ae6ba2af2f37b4df09d025d2bdfce5849aba5ba3e23a3d7c341061db4ad8002547177302a155b29c2770bc58d5b0988c1df2854e186a36d0236f9d5f0ca96e9
7
+ data.tar.gz: 57d5ab47d228ae72de01cff8e9b03db86bdb755bb70b8231a5f7639d7d45320fc647587061c76053353c4d56004a3dbefa8a52045e4bbd6e6175bc58fbc69cc3
data/.travis.yml CHANGED
@@ -1,6 +1,7 @@
1
1
  language: ruby
2
2
 
3
3
  rvm:
4
+ - 2.2
4
5
  - 2.1
5
6
  - 2.0.0
6
7
  - 1.9.3
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## v1.2.1 24 March 2015
2
+
3
+ - Add support for Postgres 9.4's `jsonb` column type (patch by [@isaacseymour](https://github.com/isaacseymour))
4
+
1
5
  ## v1.2.0 18 March 2015
2
6
 
3
7
  *Changes*
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
  ![Statesman](http://f.cl.ly/items/410n2A0S3l1W0i3i0o2K/statesman.png)
2
2
 
3
- A statesmanlike state machine library for Ruby 1.9.3 and 2.0.
3
+ A statesmanlike state machine library for Ruby 1.9.3 and up.
4
4
 
5
5
  [![Gem Version](https://badge.fury.io/rb/statesman.png)](http://badge.fury.io/rb/statesman)
6
6
  [![Build Status](https://travis-ci.org/gocardless/statesman.png?branch=master)](https://travis-ci.org/gocardless/statesman)
7
7
  [![Code Climate](https://codeclimate.com/github/gocardless/statesman.png)](https://codeclimate.com/github/gocardless/statesman)
8
8
  [![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/gocardless/statesman?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
9
9
 
10
-
11
- Statesman is a little different from other state machine libraries which tack
12
- state behaviour directly onto a model. A statesman state machine is defined as a
13
- separate class which is instantiated with the model to which it should
14
- apply. State transitions are also modelled as a class which can optionally be
15
- persisted to the database for a full audit history, including JSON metadata
16
- which can be set during a transition.
17
-
18
- This data model allows for interesting things like using a different state
19
- machine depending on the value of a model attribute.
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
- Order.first.state_machine.current_state
84
- # => "pending"
85
-
86
- Order.first.state_machine.allowed_transitions
87
- # => ["checking_out", "cancelled"]
88
-
89
- Order.first.state_machine.can_transition_to?(:cancelled)
90
- # => true/false
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. Prior to 1.0 this ignored all models in the initial state, and the `initial_state` class method was not required.
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. Prior to 1.0 this always excluded models in the initial state, and the `initial_state` class method was not required.
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 to date using an `after_transition` hook:
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 != 'json'
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 == 'json'
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"
@@ -1,3 +1,3 @@
1
1
  module Statesman
2
- VERSION = "1.2.0"
2
+ VERSION = "1.2.1"
3
3
  end
@@ -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.0
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-18 00:00:00.000000000 Z
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.2
286
+ rubygems_version: 2.4.1
287
287
  signing_key:
288
288
  specification_version: 4
289
289
  summary: A statesmanlike state machine library