statesman 1.2.0 → 1.2.1

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: 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