state_machine-audit_trail 0.0.5 → 0.1.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.
- data/lib/state_machine/audit_trail.rb +2 -6
- data/lib/state_machine/audit_trail/backend.rb +20 -2
- data/lib/state_machine/audit_trail/{active_record.rb → backend/active_record.rb} +2 -1
- data/lib/state_machine/audit_trail/backend/mongoid.rb +19 -0
- data/lib/state_machine/audit_trail/transition_auditing.rb +10 -3
- data/spec/helpers/active_record.rb +83 -0
- data/spec/helpers/mongoid.rb +72 -0
- data/spec/spec_helper.rb +0 -86
- data/spec/state_machine/active_record_spec.rb +91 -0
- data/spec/state_machine/audit_trail_spec.rb +2 -77
- data/spec/state_machine/mongoid_spec.rb +91 -0
- data/state_machine-audit_trail.gemspec +5 -3
- metadata +37 -21
@@ -2,19 +2,15 @@ require 'state_machine'
|
|
2
2
|
|
3
3
|
module StateMachine::AuditTrail
|
4
4
|
|
5
|
-
VERSION = "0.0
|
5
|
+
VERSION = "0.1.0"
|
6
6
|
|
7
7
|
def self.setup
|
8
8
|
StateMachine::Machine.send(:include, StateMachine::AuditTrail::TransitionAuditing)
|
9
9
|
end
|
10
|
-
|
11
|
-
def self.create(transition_class)
|
12
|
-
return ActiveRecord.new(transition_class) if transition_class < ::ActiveRecord::Base
|
13
|
-
raise NotImplemented, "Only support for ActiveRecord is included at this time"
|
14
|
-
end
|
15
10
|
end
|
16
11
|
|
17
12
|
require 'state_machine/audit_trail/transition_auditing'
|
18
13
|
require 'state_machine/audit_trail/backend'
|
19
14
|
require 'state_machine/audit_trail/railtie' if defined?(::Rails)
|
15
|
+
|
20
16
|
StateMachine::AuditTrail.setup
|
@@ -1,7 +1,25 @@
|
|
1
1
|
class StateMachine::AuditTrail::Backend < Struct.new(:transition_class)
|
2
|
+
|
3
|
+
autoload :Mongoid, 'state_machine/audit_trail/backend/mongoid'
|
4
|
+
autoload :ActiveRecord, 'state_machine/audit_trail/backend/active_record'
|
5
|
+
|
2
6
|
def log(object, event, from, to, timestamp = Time.now)
|
3
7
|
raise NotImplemented, "Implement in a subclass."
|
4
8
|
end
|
9
|
+
|
10
|
+
# Public creates an instance of the class which does the actual logging
|
11
|
+
#
|
12
|
+
# transition_class: the Class which holds the audit trail
|
13
|
+
#
|
14
|
+
# in order to adda new ORM here, copy audit_trail/mongoid.rb to whatever you want to call the new file and implement the #log function there
|
15
|
+
# then, return from here the appropriate object based on which ORM the transition_class is using
|
16
|
+
def self.create_for_transition_class(transition_class)
|
17
|
+
if Object.const_defined?('ActiveRecord') && transition_class.ancestors.include?(::ActiveRecord::Base)
|
18
|
+
return StateMachine::AuditTrail::Backend::ActiveRecord.new(transition_class)
|
19
|
+
elsif Object.const_defined?('Mongoid') && transition_class.ancestors.include?(::Mongoid::Document)
|
20
|
+
return StateMachine::AuditTrail::Backend::Mongoid.new(transition_class)
|
21
|
+
else
|
22
|
+
raise NotImplemented, "Only support for ActiveRecord and Mongoid is included at this time"
|
23
|
+
end
|
24
|
+
end
|
5
25
|
end
|
6
|
-
|
7
|
-
require 'state_machine/audit_trail/active_record'
|
@@ -1,4 +1,5 @@
|
|
1
|
-
class StateMachine::AuditTrail::ActiveRecord < StateMachine::AuditTrail::Backend
|
1
|
+
class StateMachine::AuditTrail::Backend::ActiveRecord < StateMachine::AuditTrail::Backend
|
2
|
+
|
2
3
|
def log(object, event, from, to, timestamp = Time.now)
|
3
4
|
# Let ActiveRecord manage the timestamp for us so it does the
|
4
5
|
# right thing with regards to timezones.
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# This is the class that does the actual logging.
|
2
|
+
# We need one of these per ORM
|
3
|
+
|
4
|
+
class StateMachine::AuditTrail::Backend::Mongoid < StateMachine::AuditTrail::Backend
|
5
|
+
|
6
|
+
# Public writes the log to the database
|
7
|
+
#
|
8
|
+
# object: the object being watched by the state_machine observer
|
9
|
+
# event: the event being observed by the state machine
|
10
|
+
# from: the state of the object prior to the event
|
11
|
+
# to: the state of the object after the event
|
12
|
+
def log(object, event, from, to, timestamp = Time.now)
|
13
|
+
tc = transition_class
|
14
|
+
foreign_key_field = tc.relations.keys.first
|
15
|
+
transition_class.create(foreign_key_field => object, :event => event, :from => from, :to => to, :create_at => timestamp)
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
end
|
@@ -1,10 +1,16 @@
|
|
1
|
+
# this class inserts the appropriate hooks into the state machine.
|
2
|
+
# it also contains functions to instantiate an object of the "transition_class"
|
3
|
+
# the transition_class is the class for the model which holds the audit_trail
|
4
|
+
|
1
5
|
module StateMachine::AuditTrail::TransitionAuditing
|
2
6
|
attr_accessor :transition_class_name
|
3
|
-
|
7
|
+
|
8
|
+
# Public tells the state machine to hook in the appropriate before / after behaviour
|
9
|
+
#
|
10
|
+
# options: a Hash of options. keys that are used are :to => CustomTransitionClass
|
4
11
|
def store_audit_trail(options = {})
|
5
12
|
state_machine = self
|
6
13
|
state_machine.transition_class_name = (options[:to] || default_transition_class_name).to_s
|
7
|
-
|
8
14
|
state_machine.after_transition do |object, transition|
|
9
15
|
state_machine.audit_trail.log(object, transition.event, transition.from, transition.to)
|
10
16
|
end
|
@@ -16,8 +22,9 @@ module StateMachine::AuditTrail::TransitionAuditing
|
|
16
22
|
end
|
17
23
|
end
|
18
24
|
|
25
|
+
# Public returns an instance of the class which does the actual audit trail logging
|
19
26
|
def audit_trail
|
20
|
-
@transition_auditor ||= StateMachine::AuditTrail.
|
27
|
+
@transition_auditor ||= StateMachine::AuditTrail::Backend.create_for_transition_class(transition_class)
|
21
28
|
end
|
22
29
|
|
23
30
|
private
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
|
3
|
+
### Setup test database
|
4
|
+
|
5
|
+
ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
|
6
|
+
|
7
|
+
ActiveRecord::Base.connection.create_table(:active_record_test_models) do |t|
|
8
|
+
t.string :state
|
9
|
+
t.string :type
|
10
|
+
t.timestamps
|
11
|
+
end
|
12
|
+
|
13
|
+
ActiveRecord::Base.connection.create_table(:active_record_test_model_with_multiple_state_machines) do |t|
|
14
|
+
t.string :first
|
15
|
+
t.string :second
|
16
|
+
t.timestamps
|
17
|
+
end
|
18
|
+
|
19
|
+
# We probably want to provide a generator for this model and the accompanying migration.
|
20
|
+
class ActiveRecordTestModelStateTransition < ActiveRecord::Base
|
21
|
+
belongs_to :test_model
|
22
|
+
end
|
23
|
+
|
24
|
+
class ActiveRecordTestModelWithMultipleStateMachinesFirstTransition < ActiveRecord::Base
|
25
|
+
belongs_to :test_model
|
26
|
+
end
|
27
|
+
|
28
|
+
class ActiveRecordTestModelWithMultipleStateMachinesSecondTransition < ActiveRecord::Base
|
29
|
+
belongs_to :test_model
|
30
|
+
end
|
31
|
+
|
32
|
+
class ActiveRecordTestModel < ActiveRecord::Base
|
33
|
+
|
34
|
+
state_machine :state, :initial => :waiting do # log initial state?
|
35
|
+
store_audit_trail
|
36
|
+
|
37
|
+
event :start do
|
38
|
+
transition [:waiting, :stopped] => :started
|
39
|
+
end
|
40
|
+
|
41
|
+
event :stop do
|
42
|
+
transition :started => :stopped
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class ActiveRecordTestModelDescendant < ActiveRecordTestModel
|
48
|
+
end
|
49
|
+
|
50
|
+
class ActiveRecordTestModelWithMultipleStateMachines < ActiveRecord::Base
|
51
|
+
|
52
|
+
state_machine :first, :initial => :beginning do
|
53
|
+
store_audit_trail
|
54
|
+
|
55
|
+
event :begin_first do
|
56
|
+
transition :beginning => :end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
state_machine :second do
|
61
|
+
store_audit_trail
|
62
|
+
|
63
|
+
event :begin_second do
|
64
|
+
transition nil => :beginning_second
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def create_transition_table(owner_class, state)
|
70
|
+
class_name = "#{owner_class.name}#{state.to_s.camelize}Transition"
|
71
|
+
|
72
|
+
ActiveRecord::Base.connection.create_table(class_name.tableize) do |t|
|
73
|
+
t.integer owner_class.name.foreign_key
|
74
|
+
t.string :event
|
75
|
+
t.string :from
|
76
|
+
t.string :to
|
77
|
+
t.datetime :created_at
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
create_transition_table(ActiveRecordTestModel, :state)
|
82
|
+
create_transition_table(ActiveRecordTestModelWithMultipleStateMachines, :first)
|
83
|
+
create_transition_table(ActiveRecordTestModelWithMultipleStateMachines, :second)
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'mongoid'
|
2
|
+
|
3
|
+
### Setup test database
|
4
|
+
|
5
|
+
Mongoid.configure do |config|
|
6
|
+
config.master = Mongo::Connection.new.db("sm_audit_trail")
|
7
|
+
end
|
8
|
+
|
9
|
+
|
10
|
+
|
11
|
+
# We probably want to provide a generator for this model and the accompanying migration.
|
12
|
+
class MongoidTestModelStateTransition
|
13
|
+
include Mongoid::Document
|
14
|
+
include Mongoid::Timestamps
|
15
|
+
belongs_to :mongoid_test_model
|
16
|
+
end
|
17
|
+
|
18
|
+
class MongoidTestModelWithMultipleStateMachinesFirstTransition
|
19
|
+
include Mongoid::Document
|
20
|
+
include Mongoid::Timestamps
|
21
|
+
belongs_to :mongoid_test_model
|
22
|
+
end
|
23
|
+
|
24
|
+
class MongoidTestModelWithMultipleStateMachinesSecondTransition
|
25
|
+
include Mongoid::Document
|
26
|
+
include Mongoid::Timestamps
|
27
|
+
belongs_to :mongoid_test_model
|
28
|
+
end
|
29
|
+
|
30
|
+
class MongoidTestModel
|
31
|
+
|
32
|
+
include Mongoid::Document
|
33
|
+
include Mongoid::Timestamps
|
34
|
+
|
35
|
+
state_machine :state, :initial => :waiting do # log initial state?
|
36
|
+
store_audit_trail :orm => :mongoid
|
37
|
+
|
38
|
+
event :start do
|
39
|
+
transition [:waiting, :stopped] => :started
|
40
|
+
end
|
41
|
+
|
42
|
+
event :stop do
|
43
|
+
transition :started => :stopped
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class MongoidTestModelDescendant < MongoidTestModel
|
49
|
+
include Mongoid::Timestamps
|
50
|
+
end
|
51
|
+
|
52
|
+
class MongoidTestModelWithMultipleStateMachines
|
53
|
+
|
54
|
+
include Mongoid::Document
|
55
|
+
include Mongoid::Timestamps
|
56
|
+
|
57
|
+
state_machine :first, :initial => :beginning do
|
58
|
+
store_audit_trail
|
59
|
+
|
60
|
+
event :begin_first do
|
61
|
+
transition :beginning => :end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
state_machine :second do
|
66
|
+
store_audit_trail
|
67
|
+
|
68
|
+
event :begin_second do
|
69
|
+
transition nil => :beginning_second
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -2,93 +2,7 @@ require 'rubygems'
|
|
2
2
|
require 'bundler/setup'
|
3
3
|
|
4
4
|
require 'rspec'
|
5
|
-
require 'active_record'
|
6
5
|
require 'state_machine/audit_trail'
|
7
6
|
|
8
|
-
|
9
|
-
### Setup test database
|
10
|
-
|
11
|
-
ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
|
12
|
-
|
13
|
-
ActiveRecord::Base.connection.create_table(:test_models) do |t|
|
14
|
-
t.string :state
|
15
|
-
t.string :type
|
16
|
-
t.timestamps
|
17
|
-
end
|
18
|
-
|
19
|
-
ActiveRecord::Base.connection.create_table(:test_model_with_multiple_state_machines) do |t|
|
20
|
-
t.string :first
|
21
|
-
t.string :second
|
22
|
-
t.timestamps
|
23
|
-
end
|
24
|
-
|
25
|
-
|
26
|
-
def create_transition_table(owner_class, state)
|
27
|
-
class_name = "#{owner_class.name}#{state.to_s.camelize}Transition"
|
28
|
-
|
29
|
-
ActiveRecord::Base.connection.create_table(class_name.tableize) do |t|
|
30
|
-
t.integer owner_class.name.foreign_key
|
31
|
-
t.string :event
|
32
|
-
t.string :from
|
33
|
-
t.string :to
|
34
|
-
t.datetime :created_at
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
|
39
|
-
# We probably want to provide a generator for this model and the accompanying migration.
|
40
|
-
class TestModelStateTransition < ActiveRecord::Base
|
41
|
-
belongs_to :test_model
|
42
|
-
end
|
43
|
-
|
44
|
-
class TestModelWithMultipleStateMachinesFirstTransition < ActiveRecord::Base
|
45
|
-
belongs_to :test_model
|
46
|
-
end
|
47
|
-
|
48
|
-
class TestModelWithMultipleStateMachinesSecondTransition < ActiveRecord::Base
|
49
|
-
belongs_to :test_model
|
50
|
-
end
|
51
|
-
|
52
|
-
class TestModel < ActiveRecord::Base
|
53
|
-
|
54
|
-
state_machine :state, :initial => :waiting do # log initial state?
|
55
|
-
store_audit_trail
|
56
|
-
|
57
|
-
event :start do
|
58
|
-
transition [:waiting, :stopped] => :started
|
59
|
-
end
|
60
|
-
|
61
|
-
event :stop do
|
62
|
-
transition :started => :stopped
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
class TestModelDescendant < TestModel
|
68
|
-
end
|
69
|
-
|
70
|
-
class TestModelWithMultipleStateMachines < ActiveRecord::Base
|
71
|
-
|
72
|
-
state_machine :first, :initial => :beginning do
|
73
|
-
store_audit_trail
|
74
|
-
|
75
|
-
event :begin_first do
|
76
|
-
transition :beginning => :end
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
state_machine :second do
|
81
|
-
store_audit_trail
|
82
|
-
|
83
|
-
event :begin_second do
|
84
|
-
transition nil => :beginning
|
85
|
-
end
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
create_transition_table(TestModel, :state)
|
90
|
-
create_transition_table(TestModelWithMultipleStateMachines, :first)
|
91
|
-
create_transition_table(TestModelWithMultipleStateMachines, :second)
|
92
|
-
|
93
7
|
RSpec.configure do |config|
|
94
8
|
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'helpers/active_record'
|
3
|
+
|
4
|
+
describe StateMachine::AuditTrail::Backend::ActiveRecord do
|
5
|
+
|
6
|
+
it "should create an ActiveRecord backend" do
|
7
|
+
backend = StateMachine::AuditTrail::Backend.create_for_transition_class(ActiveRecordTestModelStateTransition)
|
8
|
+
backend.should be_instance_of(StateMachine::AuditTrail::Backend::ActiveRecord)
|
9
|
+
end
|
10
|
+
|
11
|
+
context 'on an object with a single state machine' do
|
12
|
+
let!(:state_machine) { ActiveRecordTestModel.create! }
|
13
|
+
|
14
|
+
it "should log an event with all fields set correctly" do
|
15
|
+
state_machine.start!
|
16
|
+
last_transition = ActiveRecordTestModelStateTransition.where(:active_record_test_model_id => state_machine.id).last
|
17
|
+
|
18
|
+
last_transition.event.to_s.should == 'start'
|
19
|
+
last_transition.from.should == 'waiting'
|
20
|
+
last_transition.to.should == 'started'
|
21
|
+
last_transition.created_at.should be_within(10.seconds).of(Time.now.utc)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should log multiple events" do
|
25
|
+
lambda { state_machine.start && state_machine.stop && state_machine.start }.should change(ActiveRecordTestModelStateTransition, :count).by(3)
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should do nothing when the transition is not exectuted successfully" do
|
29
|
+
lambda { state_machine.stop }.should_not change(ActiveRecordTestModelStateTransition, :count)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'on an object with multiple state machines' do
|
34
|
+
let!(:state_machine) { ActiveRecordTestModelWithMultipleStateMachines.create! }
|
35
|
+
|
36
|
+
it "should log a state transition for the affected state machine" do
|
37
|
+
lambda { state_machine.begin_first! }.should change(ActiveRecordTestModelWithMultipleStateMachinesFirstTransition, :count).by(1)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should not log a state transition for the unaffected state machine" do
|
41
|
+
lambda { state_machine.begin_first! }.should_not change(ActiveRecordTestModelWithMultipleStateMachinesSecondTransition, :count)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'on an object with a state machine having an initial state' do
|
46
|
+
let(:state_machine_class) { ActiveRecordTestModelWithMultipleStateMachines }
|
47
|
+
let(:state_transition_class) { ActiveRecordTestModelWithMultipleStateMachinesFirstTransition }
|
48
|
+
|
49
|
+
it "should log a state transition for the inital state" do
|
50
|
+
lambda { state_machine_class.create! }.should change(state_transition_class, :count).by(1)
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should only set the :to state for the initial transition" do
|
54
|
+
state_machine_class.create!
|
55
|
+
initial_transition = state_transition_class.last
|
56
|
+
initial_transition.event.should be_nil
|
57
|
+
initial_transition.from.should be_nil
|
58
|
+
initial_transition.to.should == 'beginning'
|
59
|
+
initial_transition.created_at.should be_within(10.seconds).of(Time.now.utc)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context 'on an object with a state machine not having an initial state' do
|
64
|
+
let(:state_machine_class) { ActiveRecordTestModelWithMultipleStateMachines }
|
65
|
+
let(:state_transition_class) { ActiveRecordTestModelWithMultipleStateMachinesSecondTransition }
|
66
|
+
|
67
|
+
it "should not log a transition when the object is created" do
|
68
|
+
lambda { state_machine_class.create! }.should_not change(state_transition_class, :count)
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should log a transition for the first event" do
|
72
|
+
lambda { state_machine_class.create.begin_second! }.should change(state_transition_class, :count).by(1)
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should not set a value for the :from state on the first transition" do
|
76
|
+
state_machine_class.create.begin_second!
|
77
|
+
first_transition = state_transition_class.last
|
78
|
+
first_transition.event.to_s.should == 'begin_second'
|
79
|
+
first_transition.from.should be_nil
|
80
|
+
first_transition.to.should == 'beginning_second'
|
81
|
+
first_transition.created_at.should be_within(10.seconds).of(Time.now.utc)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
context 'on a class using STI' do
|
86
|
+
it "should properly grab the class name from STI models" do
|
87
|
+
m = ActiveRecordTestModelDescendant.create!
|
88
|
+
lambda { m.start! }.should_not raise_error
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -6,82 +6,7 @@ describe StateMachine::AuditTrail do
|
|
6
6
|
StateMachine::AuditTrail.const_defined?('VERSION').should be_true
|
7
7
|
end
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
it "should log an event with all fields set correctly" do
|
13
|
-
state_machine.start!
|
14
|
-
last_transition = TestModelStateTransition.where(:test_model_id => state_machine.id).last
|
15
|
-
|
16
|
-
last_transition.event.should == 'start'
|
17
|
-
last_transition.from.should == 'waiting'
|
18
|
-
last_transition.to.should == 'started'
|
19
|
-
last_transition.created_at.should be_within(10.seconds).of(Time.now.utc)
|
20
|
-
end
|
21
|
-
|
22
|
-
it "should log multiple events" do
|
23
|
-
lambda { state_machine.start && state_machine.stop && state_machine.start }.should change(TestModelStateTransition, :count).by(3)
|
24
|
-
end
|
25
|
-
|
26
|
-
it "should do nothing when the transition is not exectuted successfully" do
|
27
|
-
lambda { state_machine.stop }.should_not change(TestModelStateTransition, :count)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
context 'on an object with multiple state machines' do
|
32
|
-
let!(:state_machine) { TestModelWithMultipleStateMachines.create! }
|
33
|
-
|
34
|
-
it "should log a state transition for the affected state machine" do
|
35
|
-
lambda { state_machine.begin_first! }.should change(TestModelWithMultipleStateMachinesFirstTransition, :count).by(1)
|
36
|
-
end
|
37
|
-
|
38
|
-
it "should not log a state transition for the unaffected state machine" do
|
39
|
-
lambda { state_machine.begin_first! }.should_not change(TestModelWithMultipleStateMachinesSecondTransition, :count)
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
context 'on an object with a state machine having an initial state' do
|
44
|
-
let(:state_machine_class) { TestModelWithMultipleStateMachines }
|
45
|
-
let(:state_transition_class) { TestModelWithMultipleStateMachinesFirstTransition }
|
46
|
-
|
47
|
-
it "should log a state transition for the inital state" do
|
48
|
-
lambda { state_machine_class.create! }.should change(state_transition_class, :count).by(1)
|
49
|
-
end
|
50
|
-
|
51
|
-
it "should only set the :to state for the initial transition" do
|
52
|
-
state_machine_class.create!
|
53
|
-
initial_transition = state_transition_class.last
|
54
|
-
initial_transition.event.should be_nil
|
55
|
-
initial_transition.from.should be_nil
|
56
|
-
initial_transition.to.should == 'beginning'
|
57
|
-
initial_transition.created_at.should be_within(10.seconds).of(Time.now.utc)
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
context 'on an object with a state machine not having an initial state' do
|
62
|
-
let(:state_machine_class) { TestModelWithMultipleStateMachines }
|
63
|
-
let(:state_transition_class) { TestModelWithMultipleStateMachinesSecondTransition }
|
64
|
-
|
65
|
-
it "should not log a transition when the object is created" do
|
66
|
-
lambda { state_machine_class.create! }.should_not change(state_transition_class, :count)
|
67
|
-
end
|
68
|
-
|
69
|
-
it "should log a transition for the first event" do
|
70
|
-
lambda { state_machine_class.create.begin_second! }.should change(state_transition_class, :count).by(1)
|
71
|
-
end
|
72
|
-
|
73
|
-
it "should not set a value for the :from state on the first transition" do
|
74
|
-
state_machine_class.create.begin_second!
|
75
|
-
first_transition = state_transition_class.last
|
76
|
-
first_transition.event.should == 'begin_second'
|
77
|
-
first_transition.from.should be_nil
|
78
|
-
first_transition.to.should == 'beginning'
|
79
|
-
first_transition.created_at.should be_within(10.seconds).of(Time.now.utc)
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
it "should properly grab the class name from STI models" do
|
84
|
-
m = TestModelDescendant.create!
|
85
|
-
lambda { m.start! }.should_not raise_error
|
9
|
+
it "should include the auditing module into StateMachine::Machine" do
|
10
|
+
StateMachine::Machine.included_modules.should include(StateMachine::AuditTrail::TransitionAuditing)
|
86
11
|
end
|
87
12
|
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'helpers/mongoid'
|
3
|
+
|
4
|
+
describe StateMachine::AuditTrail::Backend::Mongoid do
|
5
|
+
|
6
|
+
it "should create a Mongoid backend" do
|
7
|
+
backend = StateMachine::AuditTrail::Backend.create_for_transition_class(MongoidTestModelStateTransition)
|
8
|
+
backend.should be_instance_of(StateMachine::AuditTrail::Backend::Mongoid)
|
9
|
+
end
|
10
|
+
|
11
|
+
context 'on an object with a single state machine' do
|
12
|
+
let!(:state_machine) { MongoidTestModel.create! }
|
13
|
+
|
14
|
+
it "should log an event with all fields set correctly" do
|
15
|
+
state_machine.start!
|
16
|
+
last_transition = MongoidTestModelStateTransition.where(:mongoid_test_model_id => state_machine.id).last
|
17
|
+
|
18
|
+
last_transition.event.to_s.should == 'start'
|
19
|
+
last_transition.from.should == 'waiting'
|
20
|
+
last_transition.to.should == 'started'
|
21
|
+
last_transition.created_at.should be_within(10.seconds).of(Time.now.utc)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should log multiple events" do
|
25
|
+
lambda { state_machine.start && state_machine.stop && state_machine.start }.should change(MongoidTestModelStateTransition, :count).by(3)
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should do nothing when the transition is not exectuted successfully" do
|
29
|
+
lambda { state_machine.stop }.should_not change(MongoidTestModelStateTransition, :count)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'on an object with multiple state machines' do
|
34
|
+
let!(:state_machine) { MongoidTestModelWithMultipleStateMachines.create! }
|
35
|
+
|
36
|
+
it "should log a state transition for the affected state machine" do
|
37
|
+
lambda { state_machine.begin_first! }.should change(MongoidTestModelWithMultipleStateMachinesFirstTransition, :count).by(1)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should not log a state transition for the unaffected state machine" do
|
41
|
+
lambda { state_machine.begin_first! }.should_not change(MongoidTestModelWithMultipleStateMachinesSecondTransition, :count)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'on an object with a state machine having an initial state' do
|
46
|
+
let(:state_machine_class) { MongoidTestModelWithMultipleStateMachines }
|
47
|
+
let(:state_transition_class) { MongoidTestModelWithMultipleStateMachinesFirstTransition }
|
48
|
+
|
49
|
+
it "should log a state transition for the inital state" do
|
50
|
+
lambda { state_machine_class.create! }.should change(state_transition_class, :count).by(1)
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should only set the :to state for the initial transition" do
|
54
|
+
state_machine_class.create!
|
55
|
+
initial_transition = state_transition_class.last
|
56
|
+
initial_transition.event.should be_nil
|
57
|
+
initial_transition.from.should be_nil
|
58
|
+
initial_transition.to.should == 'beginning'
|
59
|
+
initial_transition.created_at.should be_within(10.seconds).of(Time.now.utc)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context 'on an object with a state machine not having an initial state' do
|
64
|
+
let(:state_machine_class) { MongoidTestModelWithMultipleStateMachines }
|
65
|
+
let(:state_transition_class) { MongoidTestModelWithMultipleStateMachinesSecondTransition }
|
66
|
+
|
67
|
+
it "should not log a transition when the object is created" do
|
68
|
+
lambda { state_machine_class.create! }.should_not change(state_transition_class, :count)
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should log a transition for the first event" do
|
72
|
+
lambda { state_machine_class.create.begin_second! }.should change(state_transition_class, :count).by(1)
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should not set a value for the :from state on the first transition" do
|
76
|
+
state_machine_class.create.begin_second!
|
77
|
+
first_transition = state_transition_class.last
|
78
|
+
first_transition.event.to_s.should == 'begin_second'
|
79
|
+
first_transition.from.should be_nil
|
80
|
+
first_transition.to.should == 'beginning_second'
|
81
|
+
first_transition.created_at.should be_within(10.seconds).of(Time.now.utc)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
context 'on a class using STI' do
|
86
|
+
it "should properly grab the class name from STI models" do
|
87
|
+
m = MongoidTestModelDescendant.create!
|
88
|
+
lambda { m.start! }.should_not raise_error
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
2
|
Gem::Specification.new do |s|
|
3
3
|
s.name = "state_machine-audit_trail"
|
4
|
-
s.version = "0.0
|
4
|
+
s.version = "0.1.0"
|
5
5
|
s.platform = Gem::Platform::RUBY
|
6
6
|
s.authors = ["Willem van Bergen", "Jesse Storimer"]
|
7
7
|
s.email = ["willem@shopify.com", "jesse@shopify.com"]
|
@@ -17,7 +17,9 @@ Gem::Specification.new do |s|
|
|
17
17
|
s.add_development_dependency('rspec', '~> 2')
|
18
18
|
s.add_development_dependency('activerecord', '~> 3')
|
19
19
|
s.add_development_dependency('sqlite3')
|
20
|
+
s.add_development_dependency('mongoid')
|
21
|
+
s.add_development_dependency('bson_ext')
|
20
22
|
|
21
|
-
s.files = %w(.gitignore .travis.yml Gemfile LICENSE README.rdoc Rakefile lib/state_machine-audit_trail.rb lib/state_machine/audit_trail.rb lib/state_machine/audit_trail/active_record.rb lib/state_machine/audit_trail/backend.rb lib/state_machine/audit_trail/railtie.rb lib/state_machine/audit_trail/transition_auditing.rb lib/state_machine/audit_trail_generator.rb spec/spec_helper.rb spec/state_machine/audit_trail_spec.rb state_machine-audit_trail.gemspec tasks/github_gem.rb)
|
22
|
-
s.test_files = %w(spec/state_machine/audit_trail_spec.rb)
|
23
|
+
s.files = %w(.gitignore .travis.yml Gemfile LICENSE README.rdoc Rakefile lib/state_machine-audit_trail.rb lib/state_machine/audit_trail.rb lib/state_machine/audit_trail/backend.rb lib/state_machine/audit_trail/backend/active_record.rb lib/state_machine/audit_trail/backend/mongoid.rb lib/state_machine/audit_trail/railtie.rb lib/state_machine/audit_trail/transition_auditing.rb lib/state_machine/audit_trail_generator.rb spec/helpers/active_record.rb spec/helpers/mongoid.rb spec/spec_helper.rb spec/state_machine/active_record_spec.rb spec/state_machine/audit_trail_spec.rb spec/state_machine/mongoid_spec.rb state_machine-audit_trail.gemspec tasks/github_gem.rb)
|
24
|
+
s.test_files = %w(spec/state_machine/active_record_spec.rb spec/state_machine/audit_trail_spec.rb spec/state_machine/mongoid_spec.rb)
|
23
25
|
end
|
metadata
CHANGED
@@ -1,13 +1,12 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: state_machine-audit_trail
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
prerelease:
|
4
|
+
prerelease: false
|
6
5
|
segments:
|
7
6
|
- 0
|
7
|
+
- 1
|
8
8
|
- 0
|
9
|
-
|
10
|
-
version: 0.0.5
|
9
|
+
version: 0.1.0
|
11
10
|
platform: ruby
|
12
11
|
authors:
|
13
12
|
- Willem van Bergen
|
@@ -16,18 +15,16 @@ autorequire:
|
|
16
15
|
bindir: bin
|
17
16
|
cert_chain: []
|
18
17
|
|
19
|
-
date:
|
18
|
+
date: 2012-02-20 00:00:00 -05:00
|
20
19
|
default_executable:
|
21
20
|
dependencies:
|
22
21
|
- !ruby/object:Gem::Dependency
|
23
22
|
name: state_machine
|
24
23
|
prerelease: false
|
25
24
|
requirement: &id001 !ruby/object:Gem::Requirement
|
26
|
-
none: false
|
27
25
|
requirements:
|
28
26
|
- - ">="
|
29
27
|
- !ruby/object:Gem::Version
|
30
|
-
hash: 3
|
31
28
|
segments:
|
32
29
|
- 0
|
33
30
|
version: "0"
|
@@ -37,11 +34,9 @@ dependencies:
|
|
37
34
|
name: rake
|
38
35
|
prerelease: false
|
39
36
|
requirement: &id002 !ruby/object:Gem::Requirement
|
40
|
-
none: false
|
41
37
|
requirements:
|
42
38
|
- - ">="
|
43
39
|
- !ruby/object:Gem::Version
|
44
|
-
hash: 3
|
45
40
|
segments:
|
46
41
|
- 0
|
47
42
|
version: "0"
|
@@ -51,11 +46,9 @@ dependencies:
|
|
51
46
|
name: rspec
|
52
47
|
prerelease: false
|
53
48
|
requirement: &id003 !ruby/object:Gem::Requirement
|
54
|
-
none: false
|
55
49
|
requirements:
|
56
50
|
- - ~>
|
57
51
|
- !ruby/object:Gem::Version
|
58
|
-
hash: 7
|
59
52
|
segments:
|
60
53
|
- 2
|
61
54
|
version: "2"
|
@@ -65,11 +58,9 @@ dependencies:
|
|
65
58
|
name: activerecord
|
66
59
|
prerelease: false
|
67
60
|
requirement: &id004 !ruby/object:Gem::Requirement
|
68
|
-
none: false
|
69
61
|
requirements:
|
70
62
|
- - ~>
|
71
63
|
- !ruby/object:Gem::Version
|
72
|
-
hash: 5
|
73
64
|
segments:
|
74
65
|
- 3
|
75
66
|
version: "3"
|
@@ -79,16 +70,38 @@ dependencies:
|
|
79
70
|
name: sqlite3
|
80
71
|
prerelease: false
|
81
72
|
requirement: &id005 !ruby/object:Gem::Requirement
|
82
|
-
none: false
|
83
73
|
requirements:
|
84
74
|
- - ">="
|
85
75
|
- !ruby/object:Gem::Version
|
86
|
-
hash: 3
|
87
76
|
segments:
|
88
77
|
- 0
|
89
78
|
version: "0"
|
90
79
|
type: :development
|
91
80
|
version_requirements: *id005
|
81
|
+
- !ruby/object:Gem::Dependency
|
82
|
+
name: mongoid
|
83
|
+
prerelease: false
|
84
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
segments:
|
89
|
+
- 0
|
90
|
+
version: "0"
|
91
|
+
type: :development
|
92
|
+
version_requirements: *id006
|
93
|
+
- !ruby/object:Gem::Dependency
|
94
|
+
name: bson_ext
|
95
|
+
prerelease: false
|
96
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
segments:
|
101
|
+
- 0
|
102
|
+
version: "0"
|
103
|
+
type: :development
|
104
|
+
version_requirements: *id007
|
92
105
|
description: Log transitions on a state machine to support auditing and business process analytics.
|
93
106
|
email:
|
94
107
|
- willem@shopify.com
|
@@ -108,13 +121,18 @@ files:
|
|
108
121
|
- Rakefile
|
109
122
|
- lib/state_machine-audit_trail.rb
|
110
123
|
- lib/state_machine/audit_trail.rb
|
111
|
-
- lib/state_machine/audit_trail/active_record.rb
|
112
124
|
- lib/state_machine/audit_trail/backend.rb
|
125
|
+
- lib/state_machine/audit_trail/backend/active_record.rb
|
126
|
+
- lib/state_machine/audit_trail/backend/mongoid.rb
|
113
127
|
- lib/state_machine/audit_trail/railtie.rb
|
114
128
|
- lib/state_machine/audit_trail/transition_auditing.rb
|
115
129
|
- lib/state_machine/audit_trail_generator.rb
|
130
|
+
- spec/helpers/active_record.rb
|
131
|
+
- spec/helpers/mongoid.rb
|
116
132
|
- spec/spec_helper.rb
|
133
|
+
- spec/state_machine/active_record_spec.rb
|
117
134
|
- spec/state_machine/audit_trail_spec.rb
|
135
|
+
- spec/state_machine/mongoid_spec.rb
|
118
136
|
- state_machine-audit_trail.gemspec
|
119
137
|
- tasks/github_gem.rb
|
120
138
|
has_rdoc: true
|
@@ -127,29 +145,27 @@ rdoc_options: []
|
|
127
145
|
require_paths:
|
128
146
|
- lib
|
129
147
|
required_ruby_version: !ruby/object:Gem::Requirement
|
130
|
-
none: false
|
131
148
|
requirements:
|
132
149
|
- - ">="
|
133
150
|
- !ruby/object:Gem::Version
|
134
|
-
hash: 3
|
135
151
|
segments:
|
136
152
|
- 0
|
137
153
|
version: "0"
|
138
154
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
139
|
-
none: false
|
140
155
|
requirements:
|
141
156
|
- - ">="
|
142
157
|
- !ruby/object:Gem::Version
|
143
|
-
hash: 3
|
144
158
|
segments:
|
145
159
|
- 0
|
146
160
|
version: "0"
|
147
161
|
requirements: []
|
148
162
|
|
149
163
|
rubyforge_project: state_machine
|
150
|
-
rubygems_version: 1.3.
|
164
|
+
rubygems_version: 1.3.6
|
151
165
|
signing_key:
|
152
166
|
specification_version: 3
|
153
167
|
summary: Log transitions on a state machine to support auditing and business process analytics.
|
154
168
|
test_files:
|
169
|
+
- spec/state_machine/active_record_spec.rb
|
155
170
|
- spec/state_machine/audit_trail_spec.rb
|
171
|
+
- spec/state_machine/mongoid_spec.rb
|