state_machine-audit_trail 0.0.5 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|