-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
306 additions
and
76 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
module AuditTrail | ||
class ContextStack | ||
def initialize | ||
@stack = [] | ||
end | ||
|
||
def push event | ||
@stack << event | ||
end | ||
|
||
def pop | ||
@stack.pop | ||
end | ||
|
||
def current | ||
@stack.last | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,30 @@ | ||
require_relative "context_stack" | ||
|
||
module AuditTrail | ||
def self.record event_name, partition: "event", user: nil, context: nil, **params | ||
def self.record event_name, partition: nil, user: nil, context: nil, **params, &block | ||
context ||= context_stack.current | ||
partition ||= context&.partition || "event" | ||
user ||= context&.user | ||
|
||
models = params.select { |key, value| value.is_a? ActiveRecord::Base } | ||
data = params.select { |key, value| !value.is_a? ActiveRecord::Base } | ||
Event.create!(name: event_name, context: context, partition: partition, user: user, data: data, status: "completed").tap do |event| | ||
models.each { |key, model| event.links.create! name: key, model: model, partition: partition } | ||
|
||
Event.create!(name: event_name, context: context, partition: partition, user: user, data: data, status: "in_progress").tap do |event| | ||
begin | ||
context_stack.push event | ||
|
||
models.each { |key, model| event.links.create! name: key, model: model, partition: partition } | ||
|
||
event.update result: block&.call, status: "completed" | ||
rescue => ex | ||
event.update status: "failed", exception: ex | ||
ensure | ||
context_stack.pop | ||
end | ||
end | ||
end | ||
|
||
def self.context_stack | ||
Thread.current[:audit_trail_context] ||= ContextStack.new | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
require "rails_helper" | ||
|
||
RSpec.describe "Recording audit trail events within the context of other events" do | ||
context "#context" do | ||
it "records an event within the context of another event" do | ||
AuditTrail.record "some_event" do | ||
AuditTrail.record "another_event" | ||
end | ||
|
||
@event = AuditTrail::Event.last | ||
expect(@event.name).to eq "another_event" | ||
expect(@event.context.name).to eq "some_event" | ||
end | ||
|
||
it "records a hierarchy of events" do | ||
AuditTrail.record "event" do | ||
AuditTrail.record "child" do | ||
AuditTrail.record "grandchild" | ||
end | ||
end | ||
|
||
@event = AuditTrail::Event.last | ||
expect(@event.name).to eq "grandchild" | ||
expect(@event.context.name).to eq "child" | ||
expect(@event.context.context.name).to eq "event" | ||
end | ||
|
||
it "tracks the current context" do | ||
AuditTrail.record "some_event" do | ||
@event = AuditTrail::Event.find_by! name: "some_event" | ||
expect(AuditTrail.current_context).to eq @event | ||
|
||
AuditTrail.record "another_event" do | ||
@another_event = AuditTrail::Event.find_by! name: "another_event" | ||
expect(AuditTrail.current_context).to eq @another_event | ||
end | ||
end | ||
end | ||
|
||
it "removes the current context when an event completes" do | ||
AuditTrail.record "some_event" do | ||
@event = AuditTrail::Event.find_by! name: "some_event" | ||
|
||
AuditTrail.record "another_event" do | ||
raise "FAILURE" | ||
end | ||
|
||
expect(AuditTrail.context_stack.current).to eq @event | ||
|
||
raise "ANOTHER FAILURE" | ||
end | ||
|
||
expect(AuditTrail.context_stack.current).to be_nil | ||
end | ||
|
||
it "removes the current context when an event fails" do | ||
AuditTrail.record "some_event" do | ||
@event = AuditTrail::Event.find_by! name: "some_event" | ||
AuditTrail.record "another_event" do | ||
@another_event = AuditTrail::Event.find_by! name: "another_event" | ||
expect(AuditTrail.context_stack.current).to eq @another_event | ||
end | ||
expect(AuditTrail.context_stack.current).to eq @event | ||
end | ||
expect(AuditTrail.context_stack.current).to be_nil | ||
end | ||
|
||
end | ||
|
||
context "#status" do | ||
it "marks the event as in progress" do | ||
AuditTrail.record "some_event" do | ||
@event = AuditTrail::Event.last | ||
expect(@event).to be_in_progress | ||
end | ||
end | ||
|
||
it "marks the event as completed" do | ||
AuditTrail.record "some_event" do | ||
# whatever | ||
end | ||
@event = AuditTrail::Event.last | ||
expect(@event).to be_completed | ||
end | ||
|
||
it "marks the event as failed" do | ||
AuditTrail.record "some_event" do | ||
raise "BOOM" | ||
end | ||
@event = AuditTrail::Event.last | ||
expect(@event).to be_failed | ||
end | ||
end | ||
|
||
context "#result" do | ||
it "records the result of the event as a simple type" do | ||
AuditTrail.record "some_event" do | ||
:the_result | ||
end | ||
|
||
@event = AuditTrail::Event.last | ||
expect(@event.result).to eq :the_result | ||
end | ||
|
||
it "records the result of the event as a linked model" do | ||
@some_user = User.create! name: "Some person" | ||
|
||
AuditTrail.record "some_event" do | ||
@some_user | ||
end | ||
|
||
@event = AuditTrail::Event.last | ||
expect(@event.result).to eq @some_user | ||
end | ||
end | ||
|
||
context "#exception" do | ||
it "records the exception that caused the failure" do | ||
AuditTrail.record "some_event" do | ||
raise "BOOM" | ||
end | ||
|
||
@event = AuditTrail::Event.last | ||
expect(@event.exception_class).to eq "RuntimeError" | ||
expect(@event.exception_message).to eq "BOOM" | ||
end | ||
end | ||
|
||
context "inheritance" do | ||
it "inherits the partition key from the current context" do | ||
AuditTrail.record "some_event", partition: "ABC" do | ||
AuditTrail.record "another_event" | ||
end | ||
|
||
@event = AuditTrail::Event.last | ||
expect(@event.name).to eq "another_event" | ||
expect(@event.partition).to eq "ABC" | ||
end | ||
|
||
it "inherits the user from the current context" do | ||
@user = User.create! name: "Some person" | ||
|
||
AuditTrail.record "some_event", user: @user do | ||
AuditTrail.record "another_event" | ||
end | ||
|
||
@event = AuditTrail::Event.last | ||
expect(@event.name).to eq "another_event" | ||
expect(@event.user).to eq @user | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
require "rails_helper" | ||
|
||
RSpec.describe "Recording single audit trail events" do | ||
it "records the event name" do | ||
AuditTrail.record "some_event" | ||
|
||
@event = AuditTrail::Event.last | ||
expect(@event.name).to eq "some_event" | ||
end | ||
|
||
it "records a partition key" do | ||
AuditTrail.record "some_event", partition: "partition-123" | ||
|
||
@event = AuditTrail::Event.last | ||
expect(@event.partition).to eq "partition-123" | ||
end | ||
|
||
it "records the user" do | ||
@user = User.create! name: "Some person" | ||
|
||
AuditTrail.record "some_event", user: @user | ||
|
||
@event = AuditTrail::Event.last | ||
expect(@event.user).to eq @user | ||
end | ||
|
||
it "records parameters with the event" do | ||
AuditTrail.record "some_event", string: "Hello", number: 123 | ||
|
||
@event = AuditTrail::Event.last | ||
expect(@event.data).to eq({string: "Hello", number: 123}) | ||
end | ||
|
||
it "records models with the event" do | ||
@user = User.create! name: "Some person" | ||
@post = Post.create! user: @user, title: "Hello world", contents: "Welcome to my blog!" | ||
|
||
AuditTrail.record "post_added", post: @post, title: "Hello world" | ||
|
||
@event = AuditTrail::Event.last | ||
expect(@event.data).to eq({title: "Hello world"}) | ||
expect(@event.links.find_by(model: @post)).to_not be_nil | ||
end | ||
|
||
it "records the partition key along with models and the event" do | ||
@user = User.create! name: "Some person" | ||
@post = Post.create! user: @user, title: "Hello world", contents: "Welcome to my blog!" | ||
|
||
AuditTrail.record "post_added", post: @post, title: "Hello world", partition: "partition-123" | ||
|
||
@event = AuditTrail::Event.last | ||
expect(@event.data).to eq({title: "Hello world"}) | ||
expect(@event.links.find_by(model: @post).partition).to eq "partition-123" | ||
end | ||
|
||
it "records the parent event as the context for this event" do | ||
@context = AuditTrail::Event.create! name: "parent", status: "completed" | ||
|
||
AuditTrail.record "child", context: @context | ||
|
||
@event = AuditTrail::Event.last | ||
expect(@event.context).to eq @context | ||
expect(@context.children).to include @event | ||
end | ||
|
||
it "records that the event has completed" do | ||
AuditTrail.record "some_event" | ||
|
||
@event = AuditTrail::Event.last | ||
expect(@event).to be_completed | ||
end | ||
end |
Oops, something went wrong.