statesman 1.2.1 → 1.2.2
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/CHANGELOG.md +4 -0
- data/README.md +26 -2
- data/lib/statesman/adapters/active_record.rb +4 -2
- data/lib/statesman/adapters/active_record_queries.rb +12 -4
- data/lib/statesman/adapters/memory.rb +1 -1
- data/lib/statesman/adapters/mongoid.rb +1 -1
- data/lib/statesman/machine.rb +1 -1
- data/lib/statesman/version.rb +1 -1
- data/spec/spec_helper.rb +6 -1
- data/spec/statesman/adapters/active_record_spec.rb +21 -0
- data/spec/statesman/adapters/shared_examples.rb +9 -2
- data/spec/statesman/machine_spec.rb +5 -3
- data/spec/support/active_record.rb +64 -2
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a4cc83b59dfad9ed139c2215211c93550f06d4ea
|
4
|
+
data.tar.gz: 170879ce893bcd74ab1742dd609d4a9abb163ffd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a1c4175b5bc89dbe750b758f37cef8e08469515eb94f470f7872b7dcba2c7dec4650e4ed28670e7fc7d2f64647abf8dbc35e6f2ea0b980041fe38586a8eebfd9
|
7
|
+
data.tar.gz: fb34e4fd474a1ca62585e7837bc16525b872b9b69f99b65ccb10e69f06b359c2cadc14ef04f75ff90ee459d779e9a15a8769fb7c6b06a2704b84cc6c15869173
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
## v1.2.2 24 March 2015
|
2
|
+
|
3
|
+
- Add support for namespaced transition models (patch by [@DanielWright](https://github.com/DanielWright))
|
4
|
+
|
1
5
|
## v1.2.1 24 March 2015
|
2
6
|
|
3
7
|
- Add support for Postgres 9.4's `jsonb` column type (patch by [@isaacseymour](https://github.com/isaacseymour))
|
data/README.md
CHANGED
@@ -134,11 +134,12 @@ And add an association from the parent model:
|
|
134
134
|
|
135
135
|
```ruby
|
136
136
|
class Order < ActiveRecord::Base
|
137
|
-
has_many :
|
137
|
+
has_many :transitions, class_name: "OrderTransition"
|
138
138
|
|
139
139
|
# Initialize the state machine
|
140
140
|
def state_machine
|
141
|
-
@state_machine ||= OrderStateMachine.new(self, transition_class: OrderTransition
|
141
|
+
@state_machine ||= OrderStateMachine.new(self, transition_class: OrderTransition,
|
142
|
+
association_name: :transitions)
|
142
143
|
end
|
143
144
|
|
144
145
|
# Optionally delegate some methods
|
@@ -298,6 +299,29 @@ class Order < ActiveRecord::Base
|
|
298
299
|
end
|
299
300
|
```
|
300
301
|
|
302
|
+
If the transition class-name differs from the association name, you will also
|
303
|
+
need to define a corresponding `transition_name` class method:
|
304
|
+
|
305
|
+
```ruby
|
306
|
+
class Order < ActiveRecord::Base
|
307
|
+
has_many :transitions, class_name: "OrderTransition"
|
308
|
+
|
309
|
+
private
|
310
|
+
|
311
|
+
def self.transition_name
|
312
|
+
:transitions
|
313
|
+
end
|
314
|
+
|
315
|
+
def self.transition_class
|
316
|
+
OrderTransition
|
317
|
+
end
|
318
|
+
|
319
|
+
def self.initial_state
|
320
|
+
OrderStateMachine.initial_state
|
321
|
+
end
|
322
|
+
end
|
323
|
+
```
|
324
|
+
|
301
325
|
#### `Model.in_state(:state_1, :state_2, etc)`
|
302
326
|
Returns all models currently in any of the supplied states.
|
303
327
|
|
@@ -8,7 +8,7 @@ module Statesman
|
|
8
8
|
|
9
9
|
JSON_COLUMN_TYPES = %w(json jsonb).freeze
|
10
10
|
|
11
|
-
def initialize(transition_class, parent_model, observer)
|
11
|
+
def initialize(transition_class, parent_model, observer, options = {})
|
12
12
|
serialized = serialized?(transition_class)
|
13
13
|
column_type = transition_class.columns_hash['metadata'].sql_type
|
14
14
|
if !serialized && !JSON_COLUMN_TYPES.include?(column_type)
|
@@ -22,6 +22,8 @@ module Statesman
|
|
22
22
|
@transition_class = transition_class
|
23
23
|
@parent_model = parent_model
|
24
24
|
@observer = observer
|
25
|
+
@association_name =
|
26
|
+
options[:association_name] || @transition_class.table_name
|
25
27
|
end
|
26
28
|
|
27
29
|
def create(from, to, metadata = {})
|
@@ -76,7 +78,7 @@ module Statesman
|
|
76
78
|
end
|
77
79
|
|
78
80
|
def transitions_for_parent
|
79
|
-
@parent_model.send(@
|
81
|
+
@parent_model.send(@association_name)
|
80
82
|
end
|
81
83
|
|
82
84
|
def unset_old_most_recent
|
@@ -66,23 +66,31 @@ module Statesman
|
|
66
66
|
transition_class.table_name.to_sym
|
67
67
|
end
|
68
68
|
|
69
|
+
def transition_reflection
|
70
|
+
reflect_on_association(transition_name)
|
71
|
+
end
|
72
|
+
|
69
73
|
def model_foreign_key
|
70
|
-
|
74
|
+
transition_reflection.foreign_key
|
75
|
+
end
|
76
|
+
|
77
|
+
def model_table
|
78
|
+
transition_reflection.table_name
|
71
79
|
end
|
72
80
|
|
73
81
|
def transition1_join
|
74
|
-
"LEFT OUTER JOIN #{
|
82
|
+
"LEFT OUTER JOIN #{model_table} transition1
|
75
83
|
ON transition1.#{model_foreign_key} = #{table_name}.id"
|
76
84
|
end
|
77
85
|
|
78
86
|
def transition2_join
|
79
|
-
"LEFT OUTER JOIN #{
|
87
|
+
"LEFT OUTER JOIN #{model_table} transition2
|
80
88
|
ON transition2.#{model_foreign_key} = #{table_name}.id
|
81
89
|
AND transition2.sort_key > transition1.sort_key"
|
82
90
|
end
|
83
91
|
|
84
92
|
def most_recent_transition_join
|
85
|
-
"LEFT OUTER JOIN #{
|
93
|
+
"LEFT OUTER JOIN #{model_table} AS last_transition
|
86
94
|
ON #{table_name}.id = last_transition.#{model_foreign_key}
|
87
95
|
AND last_transition.most_recent = #{db_true}"
|
88
96
|
end
|
@@ -9,7 +9,7 @@ module Statesman
|
|
9
9
|
|
10
10
|
# We only accept mode as a parameter to maintain a consistent interface
|
11
11
|
# with other adapters which require it.
|
12
|
-
def initialize(transition_class, parent_model, observer)
|
12
|
+
def initialize(transition_class, parent_model, observer, _ = {})
|
13
13
|
@history = []
|
14
14
|
@transition_class = transition_class
|
15
15
|
@parent_model = parent_model
|
@@ -6,7 +6,7 @@ module Statesman
|
|
6
6
|
attr_reader :transition_class
|
7
7
|
attr_reader :parent_model
|
8
8
|
|
9
|
-
def initialize(transition_class, parent_model, observer)
|
9
|
+
def initialize(transition_class, parent_model, observer, _ = {})
|
10
10
|
@transition_class = transition_class
|
11
11
|
@parent_model = parent_model
|
12
12
|
@observer = observer
|
data/lib/statesman/machine.rb
CHANGED
@@ -183,7 +183,7 @@ module Statesman
|
|
183
183
|
@object = object
|
184
184
|
@transition_class = options[:transition_class]
|
185
185
|
@storage_adapter = adapter_class(@transition_class).new(
|
186
|
-
@transition_class, object, self)
|
186
|
+
@transition_class, object, self, options)
|
187
187
|
send(:after_initialize) if respond_to? :after_initialize
|
188
188
|
end
|
189
189
|
|
data/lib/statesman/version.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -39,7 +39,12 @@ RSpec.configure do |config|
|
|
39
39
|
end
|
40
40
|
|
41
41
|
config.before(:each, active_record: true) do
|
42
|
-
tables = %w(
|
42
|
+
tables = %w(
|
43
|
+
my_active_record_models
|
44
|
+
my_active_record_model_transitions
|
45
|
+
my_namespace_my_active_record_models
|
46
|
+
my_namespace_my_active_record_model_transitions
|
47
|
+
)
|
43
48
|
tables.each do |table_name|
|
44
49
|
sql = "DROP TABLE IF EXISTS #{table_name};"
|
45
50
|
ActiveRecord::Base.connection.execute(sql)
|
@@ -204,4 +204,25 @@ describe Statesman::Adapters::ActiveRecord, active_record: true do
|
|
204
204
|
end
|
205
205
|
end
|
206
206
|
end
|
207
|
+
|
208
|
+
context "with a namespaced model" do
|
209
|
+
before do
|
210
|
+
silence_stream(STDOUT) do
|
211
|
+
CreateNamespacedARModelMigration.migrate(:up)
|
212
|
+
CreateNamespacedARModelTransitionMigration.migrate(:up)
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
before do
|
217
|
+
MyNamespace::MyActiveRecordModelTransition.serialize(:metadata, JSON)
|
218
|
+
end
|
219
|
+
let(:observer) { double(Statesman::Machine, execute: nil) }
|
220
|
+
let(:model) do
|
221
|
+
MyNamespace::MyActiveRecordModel.create(current_state: :pending)
|
222
|
+
end
|
223
|
+
|
224
|
+
it_behaves_like "an adapter",
|
225
|
+
described_class, MyNamespace::MyActiveRecordModelTransition,
|
226
|
+
association_name: :my_active_record_model_transitions
|
227
|
+
end
|
207
228
|
end
|
@@ -11,9 +11,16 @@ require "spec_helper"
|
|
11
11
|
# history: Returns the full transition history
|
12
12
|
# last: Returns the latest transition history item
|
13
13
|
#
|
14
|
-
|
14
|
+
# rubocop:disable Metrics/LineLength
|
15
|
+
# NOTE This line cannot reasonably be shortened.
|
16
|
+
shared_examples_for "an adapter" do |adapter_class, transition_class, options = {}|
|
17
|
+
# rubocop:enable Metrics/LineLength
|
18
|
+
|
15
19
|
let(:observer) { double(Statesman::Machine, execute: nil) }
|
16
|
-
let(:adapter)
|
20
|
+
let(:adapter) do
|
21
|
+
adapter_class.new(transition_class,
|
22
|
+
model, observer, options)
|
23
|
+
end
|
17
24
|
|
18
25
|
describe "#initialize" do
|
19
26
|
subject { adapter }
|
@@ -316,21 +316,23 @@ describe Statesman::Machine do
|
|
316
316
|
context "transition class" do
|
317
317
|
it "sets a default" do
|
318
318
|
expect(Statesman.storage_adapter).to receive(:new).once.
|
319
|
-
with(Statesman::Adapters::MemoryTransition,
|
319
|
+
with(Statesman::Adapters::MemoryTransition,
|
320
|
+
my_model, anything, anything)
|
320
321
|
machine.new(my_model)
|
321
322
|
end
|
322
323
|
|
323
324
|
it "sets the passed class" do
|
324
325
|
my_transition_class = Class.new
|
325
326
|
expect(Statesman.storage_adapter).to receive(:new).once.
|
326
|
-
with(my_transition_class, my_model, anything)
|
327
|
+
with(my_transition_class, my_model, anything, anything)
|
327
328
|
machine.new(my_model, transition_class: my_transition_class)
|
328
329
|
end
|
329
330
|
|
330
331
|
it "falls back to Memory without transaction_class" do
|
331
332
|
allow(Statesman).to receive(:storage_adapter).and_return(Class.new)
|
332
333
|
expect(Statesman::Adapters::Memory).to receive(:new).once.
|
333
|
-
with(Statesman::Adapters::MemoryTransition,
|
334
|
+
with(Statesman::Adapters::MemoryTransition,
|
335
|
+
my_model, anything, anything)
|
334
336
|
machine.new(my_model)
|
335
337
|
end
|
336
338
|
end
|
@@ -37,7 +37,7 @@ class CreateMyActiveRecordModelMigration < ActiveRecord::Migration
|
|
37
37
|
def change
|
38
38
|
create_table :my_active_record_models do |t|
|
39
39
|
t.string :current_state
|
40
|
-
t.timestamps
|
40
|
+
t.timestamps null: false
|
41
41
|
end
|
42
42
|
end
|
43
43
|
end
|
@@ -59,7 +59,7 @@ class CreateMyActiveRecordModelTransitionMigration < ActiveRecord::Migration
|
|
59
59
|
t.text :metadata, default: '{}'
|
60
60
|
end
|
61
61
|
|
62
|
-
t.timestamps
|
62
|
+
t.timestamps null: false
|
63
63
|
end
|
64
64
|
|
65
65
|
add_index :my_active_record_model_transitions,
|
@@ -82,3 +82,65 @@ class DropMostRecentColumn < ActiveRecord::Migration
|
|
82
82
|
remove_column :my_active_record_model_transitions, :most_recent
|
83
83
|
end
|
84
84
|
end
|
85
|
+
|
86
|
+
module MyNamespace
|
87
|
+
class MyActiveRecordModel < ActiveRecord::Base
|
88
|
+
has_many :my_active_record_model_transitions,
|
89
|
+
class_name: "MyNamespace::MyActiveRecordModelTransition"
|
90
|
+
|
91
|
+
def self.table_name_prefix
|
92
|
+
"my_namespace_"
|
93
|
+
end
|
94
|
+
|
95
|
+
def state_machine
|
96
|
+
@state_machine ||= MyStateMachine.new(
|
97
|
+
self, transition_class: MyNameSpace::MyActiveRecordModelTransition,
|
98
|
+
association_name: :my_active_record_model_transitions)
|
99
|
+
end
|
100
|
+
|
101
|
+
def metadata
|
102
|
+
super || {}
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
class MyActiveRecordModelTransition < ActiveRecord::Base
|
107
|
+
belongs_to :my_active_record_model,
|
108
|
+
class_name: "MyNamespace::MyActiveRecordModel"
|
109
|
+
serialize :metadata, JSON
|
110
|
+
|
111
|
+
def self.table_name_prefix
|
112
|
+
"my_namespace_"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
class CreateNamespacedARModelMigration < ActiveRecord::Migration
|
118
|
+
def change
|
119
|
+
create_table :my_namespace_my_active_record_models do |t|
|
120
|
+
t.string :current_state
|
121
|
+
t.timestamps null: false
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
class CreateNamespacedARModelTransitionMigration < ActiveRecord::Migration
|
127
|
+
def change
|
128
|
+
create_table :my_namespace_my_active_record_model_transitions do |t|
|
129
|
+
t.string :to_state
|
130
|
+
t.integer :my_active_record_model_id
|
131
|
+
t.integer :sort_key
|
132
|
+
|
133
|
+
# MySQL doesn't allow default values on text fields
|
134
|
+
if ActiveRecord::Base.connection.adapter_name == 'Mysql2'
|
135
|
+
t.text :metadata
|
136
|
+
else
|
137
|
+
t.text :metadata, default: '{}'
|
138
|
+
end
|
139
|
+
|
140
|
+
t.timestamps null: false
|
141
|
+
end
|
142
|
+
|
143
|
+
add_index :my_namespace_my_active_record_model_transitions, :sort_key,
|
144
|
+
unique: true, name: 'my_namespaced_key'
|
145
|
+
end
|
146
|
+
end
|