statesman 1.2.1 → 1.2.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|