statesman 0.3.0 → 0.4.0

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: 105f8a5aeb999d07eaeb59d69bbe52ff42317895
4
- data.tar.gz: b6ac7988cea6d0a5a260ceda12be3c1ca64b38d5
3
+ metadata.gz: a7c29dcf767aa7177b818dafdc99e8645dddfcc8
4
+ data.tar.gz: 1d4db155c8fb031a4e58dafa44febca452110dcd
5
5
  SHA512:
6
- metadata.gz: 00608fb54abb049e5615c96c0903e6564a565838c4cf6db53da2151fefef2ffdabf529b18752eae466bedcd22e26237a44fb1ce1af25fcc0f1494e3164d0454a
7
- data.tar.gz: 7715676c1ea620c9509e470e285474f31fa991795abc60bb9bd45f692d90bc10453a33918567eb3a11c0cbb19a027ecf03822684b859f0d106df4a481db1c748
6
+ metadata.gz: 1aff94dc8f2c327b232c93123fa781a5f583f4b9fa08b39df79d8dd93c8600992ca67e05b277531fb58da92014eb5c1240a9d622cff3195a84266624bef7e4ef
7
+ data.tar.gz: 6882412cf48ed98015601b22a26c4d038405d1b9208a723d0fdeeddb875629e47fb267ba97ceac712bbac5572d6c3f647188047e6f17532a528baa2c6b9897ab
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ ## v0.4.0, 27 February 2014
2
+ *Additions*
3
+ - Adds after_commit flag to after_transition for callbacks to be executed after the transaction has been
4
+ committed on the ActiveRecord adapter. These callbacks will still be executed on non transactional adapters.
5
+
1
6
  ## v0.3.0, 20 February 2014
2
7
  *Additions*
3
8
  - Adds Machine#allowed_transitions method (patch by [@prikha](https://github.com/prikha))
@@ -6,26 +6,28 @@ module Statesman
6
6
  attr_reader :transition_class
7
7
  attr_reader :parent_model
8
8
 
9
- def initialize(transition_class, parent_model)
9
+ def initialize(transition_class, parent_model, observer)
10
10
  unless transition_class.serialized_attributes.include?("metadata")
11
11
  raise UnserializedMetadataError,
12
12
  "#{transition_class.name}#metadata is not serialized"
13
13
  end
14
14
  @transition_class = transition_class
15
15
  @parent_model = parent_model
16
+ @observer = observer
16
17
  end
17
18
 
18
- def create(to, before_cbs, after_cbs, metadata = {})
19
+ def create(from, to, metadata = {})
19
20
  transition = transitions_for_parent.build(to_state: to,
20
21
  sort_key: next_sort_key,
21
22
  metadata: metadata)
22
23
 
23
24
  ::ActiveRecord::Base.transaction do
24
- before_cbs.each { |cb| cb.call(@parent_model, transition) }
25
+ @observer.execute(:before, from, to, transition)
25
26
  transition.save!
26
- after_cbs.each { |cb| cb.call(@parent_model, transition) }
27
+ @observer.execute(:after, from, to, transition)
27
28
  @last_transition = nil
28
29
  end
30
+ @observer.execute(:after_commit, from, to, transition)
29
31
 
30
32
  transition
31
33
  end
@@ -9,19 +9,20 @@ 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)
12
+ def initialize(transition_class, parent_model, observer)
13
13
  @history = []
14
14
  @transition_class = transition_class
15
15
  @parent_model = parent_model
16
+ @observer = observer
16
17
  end
17
18
 
18
- def create(to, before_cbs, after_cbs, metadata = {})
19
+ def create(from, to, metadata = {})
19
20
  transition = transition_class.new(to, next_sort_key, metadata)
20
21
 
21
- before_cbs.each { |cb| cb.call(@parent_model, transition) }
22
+ @observer.execute(:before, from, to, transition)
22
23
  @history << transition
23
- after_cbs.each { |cb| cb.call(@parent_model, transition) }
24
-
24
+ @observer.execute(:after, from, to, transition)
25
+ @observer.execute(:after_commit, from, to, transition)
25
26
  transition
26
27
  end
27
28
 
@@ -6,22 +6,24 @@ module Statesman
6
6
  attr_reader :transition_class
7
7
  attr_reader :parent_model
8
8
 
9
- def initialize(transition_class, parent_model)
9
+ def initialize(transition_class, parent_model, observer)
10
10
  @transition_class = transition_class
11
11
  @parent_model = parent_model
12
+ @observer = observer
12
13
  unless transition_class_hash_fields.include?('statesman_metadata')
13
14
  raise UnserializedMetadataError, metadata_field_error_message
14
15
  end
15
16
  end
16
17
 
17
- def create(to, before_cbs, after_cbs, metadata = {})
18
+ def create(from, to, metadata = {})
18
19
  transition = transitions_for_parent.build(to_state: to,
19
20
  sort_key: next_sort_key,
20
21
  statesman_metadata: metadata)
21
22
 
22
- before_cbs.each { |cb| cb.call(@parent_model, transition) }
23
+ @observer.execute(:before, from, to, transition)
23
24
  transition.save!
24
- after_cbs.each { |cb| cb.call(@parent_model, transition) }
25
+ @observer.execute(:after, from, to, transition)
26
+ @observer.execute(:after_commit, from, to, transition)
25
27
  @last_transition = nil
26
28
  transition
27
29
  end
@@ -32,16 +32,13 @@ module Statesman
32
32
  @successors ||= {}
33
33
  end
34
34
 
35
- def before_callbacks
36
- @before_callbacks ||= []
37
- end
38
-
39
- def after_callbacks
40
- @after_callbacks ||= []
41
- end
42
-
43
- def guards
44
- @guards ||= []
35
+ def callbacks
36
+ @callbacks ||= {
37
+ before: [],
38
+ after: [],
39
+ after_commit: [],
40
+ guards: []
41
+ }
45
42
  end
46
43
 
47
44
  def transition(options = { from: nil, to: nil })
@@ -60,15 +57,17 @@ module Statesman
60
57
  to = to_s_or_nil(options[:to])
61
58
 
62
59
  validate_callback_condition(from: from, to: to)
63
- before_callbacks << Callback.new(from: from, to: to, callback: block)
60
+ callbacks[:before] << Callback.new(from: from, to: to, callback: block)
64
61
  end
65
62
 
66
- def after_transition(options = { from: nil, to: nil }, &block)
63
+ def after_transition(options = { from: nil, to: nil,
64
+ after_commit: false }, &block)
67
65
  from = to_s_or_nil(options[:from])
68
66
  to = to_s_or_nil(options[:to])
69
67
 
70
68
  validate_callback_condition(from: from, to: to)
71
- after_callbacks << Callback.new(from: from, to: to, callback: block)
69
+ phase = options[:after_commit] ? :after_commit : :after
70
+ callbacks[phase] << Callback.new(from: from, to: to, callback: block)
72
71
  end
73
72
 
74
73
  def guard_transition(options = { from: nil, to: nil }, &block)
@@ -76,7 +75,7 @@ module Statesman
76
75
  to = to_s_or_nil(options[:to])
77
76
 
78
77
  validate_callback_condition(from: from, to: to)
79
- guards << Guard.new(from: from, to: to, callback: block)
78
+ callbacks[:guards] << Guard.new(from: from, to: to, callback: block)
80
79
  end
81
80
 
82
81
  def validate_callback_condition(options = { from: nil, to: nil })
@@ -143,8 +142,9 @@ module Statesman
143
142
  transition_class: Statesman::Adapters::MemoryTransition
144
143
  })
145
144
  @object = object
145
+ @transition_class = options[:transition_class]
146
146
  @storage_adapter = Statesman.storage_adapter.new(
147
- options[:transition_class], object)
147
+ @transition_class, object, self)
148
148
  end
149
149
 
150
150
  def current_state
@@ -183,14 +183,16 @@ module Statesman
183
183
  to: new_state,
184
184
  metadata: metadata)
185
185
 
186
- before_cbs = before_callbacks_for(from: initial_state, to: new_state)
187
- after_cbs = after_callbacks_for(from: initial_state, to: new_state)
188
-
189
- @storage_adapter.create(new_state, before_cbs, after_cbs, metadata)
186
+ @storage_adapter.create(initial_state, new_state, metadata)
190
187
 
191
188
  true
192
189
  end
193
190
 
191
+ def execute(phase, initial_state, new_state, transition)
192
+ callbacks = callbacks_for(phase, from: initial_state, to: new_state)
193
+ callbacks.each { |cb| cb.call(@object, transition) }
194
+ end
195
+
194
196
  def transition_to(new_state, metadata = nil)
195
197
  self.transition_to!(new_state, metadata)
196
198
  rescue
@@ -204,15 +206,11 @@ module Statesman
204
206
  end
205
207
 
206
208
  def guards_for(options = { from: nil, to: nil })
207
- select_callbacks_for(self.class.guards, options)
208
- end
209
-
210
- def before_callbacks_for(options = { from: nil, to: nil })
211
- select_callbacks_for(self.class.before_callbacks, options)
209
+ select_callbacks_for(self.class.callbacks[:guards], options)
212
210
  end
213
211
 
214
- def after_callbacks_for(options = { from: nil, to: nil })
215
- select_callbacks_for(self.class.after_callbacks, options)
212
+ def callbacks_for(phase, options = { from: nil, to: nil })
213
+ select_callbacks_for(self.class.callbacks[phase], options)
216
214
  end
217
215
 
218
216
  def select_callbacks_for(callbacks, options = { from: nil, to: nil })
@@ -1,3 +1,3 @@
1
1
  module Statesman
2
- VERSION = "0.3.0"
2
+ VERSION = "0.4.0"
3
3
  end
@@ -9,7 +9,11 @@ describe Statesman::Adapters::ActiveRecord do
9
9
  end
10
10
 
11
11
  before { MyActiveRecordModelTransition.serialize(:metadata, JSON) }
12
-
12
+ let(:observer) do
13
+ result = double(Statesman::Machine)
14
+ result.stub(:execute)
15
+ result
16
+ end
13
17
  let(:model) { MyActiveRecordModel.create(current_state: :pending) }
14
18
  it_behaves_like "an adapter", described_class, MyActiveRecordModelTransition
15
19
 
@@ -20,18 +24,20 @@ describe Statesman::Adapters::ActiveRecord do
20
24
  it "raises an exception if metadata is not serialized" do
21
25
  expect do
22
26
  described_class.new(MyActiveRecordModelTransition,
23
- MyActiveRecordModel)
27
+ MyActiveRecordModel, observer)
24
28
  end.to raise_exception(Statesman::UnserializedMetadataError)
25
29
  end
26
30
  end
27
31
  end
28
32
 
29
33
  describe "#last" do
30
- let(:adapter) { described_class.new(MyActiveRecordModelTransition, model) }
34
+ let(:adapter) do
35
+ described_class.new(MyActiveRecordModelTransition, model, observer)
36
+ end
31
37
 
32
38
  context "with a previously looked up transition" do
33
39
  before do
34
- adapter.create(:y, [], [])
40
+ adapter.create(:x, :y)
35
41
  adapter.last
36
42
  end
37
43
 
@@ -42,7 +48,7 @@ describe Statesman::Adapters::ActiveRecord do
42
48
  end
43
49
 
44
50
  context "and a new transition" do
45
- before { adapter.create(:z, [], []) }
51
+ before { adapter.create(:y, :z, []) }
46
52
  it "retrieves the new transition from the database" do
47
53
  expect(adapter.last.to_state).to eq("z")
48
54
  end
@@ -9,7 +9,11 @@ describe Statesman::Adapters::Mongoid, mongo: true do
9
9
  after do
10
10
  Mongoid.purge!
11
11
  end
12
-
12
+ let(:observer) do
13
+ result = double(Statesman::Machine)
14
+ result.stub(:execute)
15
+ result
16
+ end
13
17
  let(:model) { MyMongoidModel.create(current_state: :pending) }
14
18
  it_behaves_like "an adapter", described_class, MyMongoidModelTransition
15
19
 
@@ -21,18 +25,21 @@ describe Statesman::Adapters::Mongoid, mongo: true do
21
25
 
22
26
  it "raises an exception if metadata is not serialized" do
23
27
  expect do
24
- described_class.new(MyMongoidModelTransition, MyMongoidModel)
28
+ described_class.new(MyMongoidModelTransition, MyMongoidModel,
29
+ observer)
25
30
  end.to raise_exception(Statesman::UnserializedMetadataError)
26
31
  end
27
32
  end
28
33
  end
29
34
 
30
35
  describe "#last" do
31
- let(:adapter) { described_class.new(MyMongoidModelTransition, model) }
36
+ let(:adapter) do
37
+ described_class.new(MyMongoidModelTransition, model, observer)
38
+ end
32
39
 
33
40
  context "with a previously looked up transition" do
34
41
  before do
35
- adapter.create(:y, [], [])
42
+ adapter.create(:x, :y)
36
43
  adapter.last
37
44
  end
38
45
 
@@ -43,7 +50,7 @@ describe Statesman::Adapters::Mongoid, mongo: true do
43
50
  end
44
51
 
45
52
  context "and a new transition" do
46
- before { adapter.create(:z, [], []) }
53
+ before { adapter.create(:y, :z) }
47
54
  it "retrieves the new transition from the database" do
48
55
  expect(adapter.last.to_state).to eq("z")
49
56
  end
@@ -12,9 +12,12 @@ require "spec_helper"
12
12
  # last: Returns the latest transition history item
13
13
  #
14
14
  shared_examples_for "an adapter" do |adapter_class, transition_class|
15
- let(:adapter) { adapter_class.new(transition_class, model) }
16
- let(:before_cbs) { [] }
17
- let(:after_cbs) { [] }
15
+ let(:observer) do
16
+ result = double(Statesman::Machine)
17
+ result.stub(:execute)
18
+ result
19
+ end
20
+ let(:adapter) { adapter_class.new(transition_class, model, observer) }
18
21
 
19
22
  describe "#initialize" do
20
23
  subject { adapter }
@@ -24,8 +27,9 @@ shared_examples_for "an adapter" do |adapter_class, transition_class|
24
27
  end
25
28
 
26
29
  describe "#create" do
30
+ let(:from) { :x }
27
31
  let(:to) { :y }
28
- let(:create) { adapter.create(to, before_cbs, after_cbs) }
32
+ let(:create) { adapter.create(from, to) }
29
33
  subject { -> { create } }
30
34
 
31
35
  it { should change(adapter.history, :count).by(1) }
@@ -43,43 +47,35 @@ shared_examples_for "an adapter" do |adapter_class, transition_class|
43
47
  end
44
48
 
45
49
  context "with a previous transition" do
46
- before { adapter.create(:x, before_cbs, after_cbs) }
50
+ before { adapter.create(from, to) }
47
51
  its(:sort_key) { should be(10) }
48
52
  end
49
53
  end
50
54
 
51
55
  context "with before callbacks" do
52
- let(:callback) { double(call: true) }
53
- let(:before_cbs) { [callback] }
54
-
55
56
  it "is called before the state transition" do
56
- callback.should_receive(:call).with do |passed_model, transition|
57
- expect(passed_model).to be(model)
58
- expect(transition).to be_a(adapter.transition_class)
59
- expect(adapter.history.length).to eq(0)
57
+ observer.should_receive(:execute).with do
58
+ |phase, from_state, to_state, transition|
59
+ expect(adapter.history.length).to eq(0) if phase == :before
60
60
  end.once
61
-
62
- adapter.create(:x, before_cbs, after_cbs)
61
+ adapter.create(from, to)
63
62
  expect(adapter.history.length).to eq(1)
64
63
  end
65
64
  end
66
65
 
67
66
  context "with after callbacks" do
68
- let(:callback) { double(call: true) }
69
- let(:after_cbs) { [callback] }
70
-
71
67
  it "is called after the state transition" do
72
- callback.should_receive(:call).with do |passed_model, transition|
73
- expect(passed_model).to be(model)
74
- expect(adapter.last).to eq(transition)
68
+ observer.should_receive(:execute).with do
69
+ |phase, from_state, to_state, transition|
70
+ expect(adapter.last).to eq(transition) if phase == :after
75
71
  end.once
76
- adapter.create(:x, before_cbs, after_cbs)
72
+ adapter.create(from, to)
77
73
  end
78
74
  end
79
75
 
80
76
  context "with metadata" do
81
77
  let(:metadata) { { "some" => "hash" } }
82
- subject { adapter.create(to, before_cbs, after_cbs, metadata) }
78
+ subject { adapter.create(from, to, metadata) }
83
79
  its(:metadata) { should eq(metadata) }
84
80
  end
85
81
  end
@@ -89,12 +85,11 @@ shared_examples_for "an adapter" do |adapter_class, transition_class|
89
85
  it { should eq([]) }
90
86
 
91
87
  context "with transitions" do
92
- let!(:transition) { adapter.create(:y, before_cbs, after_cbs) }
88
+ let!(:transition) { adapter.create(:x, :y) }
93
89
  it { should eq([transition]) }
94
90
 
95
91
  context "sorting" do
96
- let!(:transition) { adapter.create(:y, before_cbs, after_cbs) }
97
- let!(:transition2) { adapter.create(:y, before_cbs, after_cbs) }
92
+ let!(:transition2) { adapter.create(:x, :y) }
98
93
  subject { adapter.history }
99
94
  it { should eq(adapter.history.sort_by(&:sort_key)) }
100
95
  end
@@ -103,8 +98,8 @@ shared_examples_for "an adapter" do |adapter_class, transition_class|
103
98
 
104
99
  describe "#last" do
105
100
  before do
106
- adapter.create(:y, before_cbs, after_cbs)
107
- adapter.create(:z, before_cbs, after_cbs)
101
+ adapter.create(:x, :y)
102
+ adapter.create(:y, :z)
108
103
  end
109
104
  subject { adapter.last }
110
105
 
@@ -127,13 +127,12 @@ describe Statesman::Machine do
127
127
  it "stores callbacks" do
128
128
  expect do
129
129
  machine.send(assignment_method) {}
130
- end.to change(machine.send(callback_store), :count).by(1)
130
+ end.to change(machine.callbacks[callback_store], :count).by(1)
131
131
  end
132
132
 
133
133
  it "stores callback instances" do
134
134
  machine.send(assignment_method) {}
135
-
136
- machine.send(callback_store).each do |callback|
135
+ machine.callbacks[callback_store].each do |callback|
137
136
  expect(callback).to be_a(Statesman::Callback)
138
137
  end
139
138
  end
@@ -174,11 +173,11 @@ describe Statesman::Machine do
174
173
  end
175
174
 
176
175
  describe ".before_transition" do
177
- it_behaves_like "a callback store", :before_transition, :before_callbacks
176
+ it_behaves_like "a callback store", :before_transition, :before
178
177
  end
179
178
 
180
179
  describe ".after_transition" do
181
- it_behaves_like "a callback store", :after_transition, :after_callbacks
180
+ it_behaves_like "a callback store", :after_transition, :after
182
181
  end
183
182
 
184
183
  describe ".guard_transition" do
@@ -194,14 +193,14 @@ describe Statesman::Machine do
194
193
  context "transition class" do
195
194
  it "sets a default" do
196
195
  Statesman.storage_adapter.should_receive(:new).once
197
- .with(Statesman::Adapters::MemoryTransition, my_model)
196
+ .with(Statesman::Adapters::MemoryTransition, my_model, anything)
198
197
  machine.new(my_model)
199
198
  end
200
199
 
201
200
  it "sets the passed class" do
202
201
  my_transition_class = Class.new
203
202
  Statesman.storage_adapter.should_receive(:new).once
204
- .with(my_transition_class, my_model)
203
+ .with(my_transition_class, my_model, anything)
205
204
  machine.new(my_model, transition_class: my_transition_class)
206
205
  end
207
206
  end
@@ -396,28 +395,6 @@ describe Statesman::Machine do
396
395
  end
397
396
  end
398
397
  end
399
-
400
- context "with a before callback" do
401
- let(:callbacks) { [] }
402
- before { instance.stub(:before_callbacks_for).and_return(callbacks) }
403
-
404
- it "is passed to the adapter" do
405
- Statesman::Adapters::Memory.any_instance.should_receive(:create)
406
- .with("y", callbacks, anything, anything)
407
- instance.transition_to!(:y)
408
- end
409
- end
410
-
411
- context "with an after callback" do
412
- let(:callbacks) { [] }
413
- before { instance.stub(:after_callbacks_for).and_return(callbacks) }
414
-
415
- it "is passed to the adapter" do
416
- Statesman::Adapters::Memory.any_instance.should_receive(:create)
417
- .with("y", anything, callbacks, anything)
418
- instance.transition_to!(:y)
419
- end
420
- end
421
398
  end
422
399
  end
423
400
 
@@ -442,7 +419,7 @@ describe Statesman::Machine do
442
419
  end
443
420
  end
444
421
 
445
- shared_examples "a callback filter" do |definer, getter|
422
+ shared_examples "a callback filter" do |definer, phase|
446
423
  before do
447
424
  machine.class_eval do
448
425
  state :x
@@ -454,7 +431,7 @@ describe Statesman::Machine do
454
431
  end
455
432
 
456
433
  let(:instance) { machine.new(my_model) }
457
- let(:callbacks) { instance.send(getter, from: :x, to: :y) }
434
+ let(:callbacks) { instance.send(:callbacks_for, phase, from: :x, to: :y) }
458
435
 
459
436
  context "with no defined callbacks" do
460
437
  specify { expect(callbacks).to eq([]) }
@@ -480,16 +457,16 @@ describe Statesman::Machine do
480
457
  end
481
458
 
482
459
  describe "#guards_for" do
483
- it_behaves_like "a callback filter", :guard_transition, :guards_for
460
+ it_behaves_like "a callback filter", :guard_transition, :guards
484
461
  end
485
462
 
486
463
  describe "#before_callbacks_for" do
487
464
  it_behaves_like "a callback filter", :before_transition,
488
- :before_callbacks_for
465
+ :before
489
466
  end
490
467
 
491
468
  describe "#after_callbacks_for" do
492
469
  it_behaves_like "a callback filter", :after_transition,
493
- :after_callbacks_for
470
+ :after
494
471
  end
495
472
  end
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: 0.3.0
4
+ version: 0.4.0
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: 2014-02-20 00:00:00.000000000 Z
12
+ date: 2014-03-10 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler