action_auditor 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/.gitignore +3 -0
- data/README.rdoc +5 -0
- data/Rakefile +1 -0
- data/VERSION +1 -0
- data/generators/action_auditor_migration/USAGE +4 -0
- data/generators/action_auditor_migration/action_auditor_migration_generator.rb +15 -0
- data/generators/action_auditor_migration/templates/migration.rb +16 -0
- data/install.rb +1 -0
- data/lib/action_auditor.rb +13 -0
- data/lib/action_auditor/auditor/active_record.rb +27 -0
- data/lib/action_auditor/auditor/base.rb +7 -0
- data/lib/action_auditor/auditor/simple.rb +33 -0
- data/lib/action_auditor/extensions/action_controller.rb +107 -0
- data/rails/init.rb +3 -0
- data/spec/action_auditor_spec.rb +1 -0
- data/spec/controllers/controller_integration_spec.rb +116 -0
- data/spec/spec_helper.rb +10 -0
- data/tasks/rdoc.rake +17 -0
- data/tasks/spec.rake +45 -0
- data/uninstall.rb +1 -0
- metadata +76 -0
data/.gitignore
ADDED
data/README.rdoc
ADDED
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Dir["#{File.dirname(__FILE__)}/tasks/*.rake"].sort.each { |ext| load ext }
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class ActionAuditorMigrationGenerator < Rails::Generator::Base
|
2
|
+
def initialize(runtime_args, runtime_options = {})
|
3
|
+
super
|
4
|
+
end
|
5
|
+
|
6
|
+
def manifest
|
7
|
+
record do |m|
|
8
|
+
m.migration_template 'migration.rb', "db/migrate"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def file_name
|
13
|
+
"create_action_auditor_table"
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class CreateActionAuditorTable < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :logged_actions do |t|
|
4
|
+
t.belongs_to :scope, :polymorphic => true
|
5
|
+
t.text :message
|
6
|
+
t.text :parameters
|
7
|
+
t.timestamp :created_at
|
8
|
+
end
|
9
|
+
|
10
|
+
add_index :logged_actions, [ :scope_type, :scope_id, :created_at ]
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.down
|
14
|
+
drop_table :logged_actions
|
15
|
+
end
|
16
|
+
end
|
data/install.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# Install hook code here
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module ActionAuditor
|
2
|
+
def self.auditors
|
3
|
+
@@auditors ||= []
|
4
|
+
end
|
5
|
+
|
6
|
+
def self.auditors=(auditors)
|
7
|
+
@@auditors = Array(auditors)
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.log(message, parameters = {})
|
11
|
+
auditors.each { |auditor| auditor.log(message, parameters) }
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module ActionAuditor
|
2
|
+
module Auditor
|
3
|
+
class ActiveRecord < Base
|
4
|
+
class LoggedAction < ::ActiveRecord::Base
|
5
|
+
set_table_name "logged_actions"
|
6
|
+
|
7
|
+
serialize :parameters
|
8
|
+
end
|
9
|
+
|
10
|
+
def clear!
|
11
|
+
LoggedAction.destroy_all
|
12
|
+
end
|
13
|
+
|
14
|
+
def log(message, parameters = {})
|
15
|
+
LoggedAction.create :message => message, :parameters => parameters
|
16
|
+
end
|
17
|
+
|
18
|
+
def size
|
19
|
+
LoggedAction.count
|
20
|
+
end
|
21
|
+
|
22
|
+
def last
|
23
|
+
LoggedAction.last
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module ActionAuditor
|
2
|
+
module Auditor
|
3
|
+
# The simplest possible implementation of an auditor.
|
4
|
+
# Simply maintains a list of [message, hash] pairs,
|
5
|
+
# with no persistence.
|
6
|
+
# This is mainly of use for testing.
|
7
|
+
class Simple < Base
|
8
|
+
class LoggedAction < Struct.new(:message, :parameters)
|
9
|
+
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@messages = []
|
14
|
+
end
|
15
|
+
|
16
|
+
def clear!
|
17
|
+
@messages = []
|
18
|
+
end
|
19
|
+
|
20
|
+
def log(message, parameters = {})
|
21
|
+
@messages << LoggedAction.new(message, parameters)
|
22
|
+
end
|
23
|
+
|
24
|
+
def size
|
25
|
+
@messages.size
|
26
|
+
end
|
27
|
+
|
28
|
+
def last
|
29
|
+
@messages.last
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
module ActionAuditor
|
2
|
+
module Extensions
|
3
|
+
module ActionController
|
4
|
+
module ClassMethods
|
5
|
+
# Set up an audit trail for the next action defined.
|
6
|
+
# This is similar to +desc+ in a Rake task: you call it
|
7
|
+
# immediately before the action.
|
8
|
+
#
|
9
|
+
# == Simple messages
|
10
|
+
# This will log "hello, world" each time someone
|
11
|
+
# visits the +index+ page.
|
12
|
+
# class PostsController < ApplicationController
|
13
|
+
# audit "hello, world"
|
14
|
+
# def index
|
15
|
+
# end
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# == Blocks
|
19
|
+
# If you need to interpolate any information at runtime,
|
20
|
+
# you'll need to use a block:
|
21
|
+
# class PostsController < ApplicationController
|
22
|
+
# audit { "#{current_user} stopped by" }
|
23
|
+
# def index
|
24
|
+
# end
|
25
|
+
# end
|
26
|
+
# ...otherwise the string will get evaluated when the
|
27
|
+
# controller class is defined.
|
28
|
+
#
|
29
|
+
# == Parameters
|
30
|
+
# If you need to save any information besides a message,
|
31
|
+
# you can write a block that returns [+message+, +params+],
|
32
|
+
# where +params+ is a hash of objects:
|
33
|
+
# class PostsController < ApplicationController
|
34
|
+
# audit {[
|
35
|
+
# "{{user}} created {{post}}",
|
36
|
+
# { :user => current_user, :post => @post }
|
37
|
+
# ]}
|
38
|
+
# def create
|
39
|
+
# @post = Post.create(params[:post])
|
40
|
+
# end
|
41
|
+
# end
|
42
|
+
# Notice that you can interpolate these parameters
|
43
|
+
# within your log message.
|
44
|
+
#
|
45
|
+
# It's up to the individual auditors how your parameters
|
46
|
+
# are saved — and, indeed, whether they're saved at all.
|
47
|
+
# An ActiveRecord implementation might serialize the
|
48
|
+
# objects, or just their IDs, while a text-only auditor
|
49
|
+
# might discard them completely. You should not rely on
|
50
|
+
# having access to these parameters later on, unless
|
51
|
+
# you know how and where they are saved.
|
52
|
+
def audit(*args, &block)
|
53
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
54
|
+
block_or_message = block_given? ? block : args.shift
|
55
|
+
@pending_auditor = [ args, options, block_or_message ]
|
56
|
+
end
|
57
|
+
|
58
|
+
def method_added_with_auditing(name) #:nodoc:
|
59
|
+
method_added_without_auditing(name)
|
60
|
+
if @pending_auditor
|
61
|
+
auditors[name.to_sym] = @pending_auditor
|
62
|
+
@pending_auditor = nil
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Filter method called after each action.
|
68
|
+
# Checks if there's any auditing set up for this
|
69
|
+
# action, then logs relevant information using
|
70
|
+
# any active auditors.
|
71
|
+
def audit_last_action
|
72
|
+
if auditor = self.class.auditors[action_name.to_sym]
|
73
|
+
args, options, block_or_message = auditor
|
74
|
+
parameters = {}
|
75
|
+
|
76
|
+
log_message, parameters = case block_or_message
|
77
|
+
when String then [ block_or_message, {} ]
|
78
|
+
when Proc
|
79
|
+
values = Array(instance_eval(&block_or_message))
|
80
|
+
values << {} if values.size < 2
|
81
|
+
values[0,2]
|
82
|
+
end
|
83
|
+
|
84
|
+
log_message.gsub!(/\{\{(\w+)\}\}/) { parameters[$1.to_sym].to_s }
|
85
|
+
|
86
|
+
ActionAuditor.log(log_message, parameters)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
protected :audit_last_action
|
90
|
+
|
91
|
+
def self.included(receiver) #:nodoc:
|
92
|
+
receiver.extend ClassMethods
|
93
|
+
|
94
|
+
receiver.class_eval do
|
95
|
+
class_inheritable_accessor :auditors
|
96
|
+
self.auditors = {}
|
97
|
+
|
98
|
+
class << receiver
|
99
|
+
alias_method_chain :method_added, :auditing
|
100
|
+
end
|
101
|
+
|
102
|
+
after_filter :audit_last_action
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
data/rails/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
@@ -0,0 +1,116 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
|
3
|
+
class Thing
|
4
|
+
attr_accessor :attributes
|
5
|
+
|
6
|
+
def initialize(attributes = {})
|
7
|
+
self.attributes = attributes
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_s
|
11
|
+
"Thing(#{attributes.collect { |k, v| "#{k}:#{v}" }.join(", ")})"
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.clear!
|
15
|
+
@things = []
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.create(params = {})
|
19
|
+
returning new(params) do |thing|
|
20
|
+
@things << thing
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.last
|
25
|
+
@things.last
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class TestController < ActionController::Base
|
30
|
+
audit "Someone looked at something"
|
31
|
+
def simple
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
def unlogged
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
audit { "Message from #{current_user}" }
|
40
|
+
def substituted
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
audit { [ "{{user}} created {{thing}}", { :user => current_user, :thing => @thing } ] }
|
45
|
+
def complex
|
46
|
+
@thing = Thing.create :colour => :red
|
47
|
+
end
|
48
|
+
|
49
|
+
protected
|
50
|
+
def current_user
|
51
|
+
"Matt"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe TestController do
|
56
|
+
[
|
57
|
+
ActionAuditor::Auditor::Simple.new,
|
58
|
+
ActionAuditor::Auditor::ActiveRecord.new
|
59
|
+
].each do |auditor|
|
60
|
+
describe "with #{auditor.class.name.demodulize} auditing" do
|
61
|
+
before :each do
|
62
|
+
ActionAuditor.auditors = auditor
|
63
|
+
Thing.clear!
|
64
|
+
auditor.clear!
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should allow auditing" do
|
68
|
+
controller.class.should respond_to(:audit)
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should log visits to '/index" do
|
72
|
+
lambda {
|
73
|
+
get :simple
|
74
|
+
}.should change(auditor, :size).by(1)
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should provide a message on visits to /simple" do
|
78
|
+
get :simple
|
79
|
+
auditor.last.message.should == "Someone looked at something"
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should not log visits to /unlogged" do
|
83
|
+
lambda {
|
84
|
+
get :unlogged
|
85
|
+
}.should_not change(auditor, :size)
|
86
|
+
end
|
87
|
+
|
88
|
+
it "should log visits to /substituted" do
|
89
|
+
lambda {
|
90
|
+
get :substituted
|
91
|
+
}.should change(auditor, :size).by(1)
|
92
|
+
end
|
93
|
+
|
94
|
+
it "should keep a correct log of visits to /substituted" do
|
95
|
+
get :substituted
|
96
|
+
auditor.last.message.should == "Message from Matt"
|
97
|
+
auditor.last.parameters.should be_empty
|
98
|
+
end
|
99
|
+
|
100
|
+
it "should log visits to /complex" do
|
101
|
+
lambda {
|
102
|
+
get :complex
|
103
|
+
}.should change(auditor, :size).by(1)
|
104
|
+
end
|
105
|
+
|
106
|
+
it "should keep a correct log of visits to /complex" do
|
107
|
+
get :complex
|
108
|
+
log = auditor.last
|
109
|
+
log.message.should == "Matt created Thing(colour:red)"
|
110
|
+
log.parameters[:user].should == "Matt"
|
111
|
+
log.parameters[:thing].should be_a(Thing)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
begin
|
2
|
+
require File.dirname(__FILE__) + '/../../../../spec/spec_helper'
|
3
|
+
rescue LoadError
|
4
|
+
puts "You need to install rspec in your base app"
|
5
|
+
exit
|
6
|
+
end
|
7
|
+
|
8
|
+
plugin_spec_dir = File.dirname(__FILE__)
|
9
|
+
ActiveRecord::Base.logger = Logger.new(plugin_spec_dir + "/debug.log")
|
10
|
+
|
data/tasks/rdoc.rake
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
begin
|
2
|
+
require 'hanna/rdoctask'
|
3
|
+
rescue
|
4
|
+
require 'rake/rdoctask'
|
5
|
+
end
|
6
|
+
|
7
|
+
desc 'Generate RDoc documentation.'
|
8
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
9
|
+
rdoc.rdoc_files.include('README.rdoc').
|
10
|
+
include('lib/**/*.rb')
|
11
|
+
|
12
|
+
rdoc.main = "README.rdoc" # page to start on
|
13
|
+
rdoc.title = "ActionAuditor documentation"
|
14
|
+
|
15
|
+
rdoc.rdoc_dir = 'doc' # rdoc output folder
|
16
|
+
rdoc.options << '--webcvs=http://github.com/fauxparse/action_auditor/'
|
17
|
+
end
|
data/tasks/spec.rake
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# Don't load rspec if running "rake gems:*"
|
2
|
+
unless ARGV.any? {|a| a =~ /^gems/}
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'spec/rake/spectask'
|
6
|
+
rescue MissingSourceFile
|
7
|
+
module Spec
|
8
|
+
module Rake
|
9
|
+
class SpecTask
|
10
|
+
def initialize(name)
|
11
|
+
task name do
|
12
|
+
raise <<-MSG
|
13
|
+
|
14
|
+
#{"*" * 80}
|
15
|
+
* You are trying to run an rspec rake task defined in
|
16
|
+
* #{__FILE__},
|
17
|
+
* but rspec cannot be found.
|
18
|
+
#{"*" * 80}
|
19
|
+
MSG
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
task :default => :spec
|
28
|
+
|
29
|
+
desc "Run all specs in spec directory (excluding plugin specs)"
|
30
|
+
Spec::Rake::SpecTask.new(:spec) do |t|
|
31
|
+
t.spec_opts = ['--colour --format progress --loadby mtime --reverse']
|
32
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
33
|
+
end
|
34
|
+
|
35
|
+
namespace :spec do
|
36
|
+
desc "Run all specs in spec directory with RCov"
|
37
|
+
Spec::Rake::SpecTask.new(:rcov) do |t|
|
38
|
+
t.spec_opts = ['--colour --format progress --loadby mtime --reverse']
|
39
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
40
|
+
t.rcov = true
|
41
|
+
t.rcov_opts = ['--text-report --rails --exclude "spec/*,application_controller.rb,application_helper.rb"']
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
data/uninstall.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# Uninstall hook code here
|
metadata
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: action_auditor
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Matt Powell
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-11-27 00:00:00 +13:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: Keep an audit trail of actions in your application
|
17
|
+
email: fauxparse@gmail.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README.rdoc
|
24
|
+
files:
|
25
|
+
- .gitignore
|
26
|
+
- README.rdoc
|
27
|
+
- Rakefile
|
28
|
+
- VERSION
|
29
|
+
- generators/action_auditor_migration/USAGE
|
30
|
+
- generators/action_auditor_migration/action_auditor_migration_generator.rb
|
31
|
+
- generators/action_auditor_migration/templates/migration.rb
|
32
|
+
- install.rb
|
33
|
+
- lib/action_auditor.rb
|
34
|
+
- lib/action_auditor/auditor/active_record.rb
|
35
|
+
- lib/action_auditor/auditor/base.rb
|
36
|
+
- lib/action_auditor/auditor/simple.rb
|
37
|
+
- lib/action_auditor/extensions/action_controller.rb
|
38
|
+
- rails/init.rb
|
39
|
+
- spec/action_auditor_spec.rb
|
40
|
+
- spec/controllers/controller_integration_spec.rb
|
41
|
+
- spec/spec_helper.rb
|
42
|
+
- tasks/rdoc.rake
|
43
|
+
- tasks/spec.rake
|
44
|
+
- uninstall.rb
|
45
|
+
has_rdoc: true
|
46
|
+
homepage: http://github.com/fauxparse/action_auditor
|
47
|
+
licenses: []
|
48
|
+
|
49
|
+
post_install_message:
|
50
|
+
rdoc_options:
|
51
|
+
- --charset=UTF-8
|
52
|
+
require_paths:
|
53
|
+
- lib
|
54
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: "0"
|
59
|
+
version:
|
60
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: "0"
|
65
|
+
version:
|
66
|
+
requirements: []
|
67
|
+
|
68
|
+
rubyforge_project:
|
69
|
+
rubygems_version: 1.3.5
|
70
|
+
signing_key:
|
71
|
+
specification_version: 3
|
72
|
+
summary: Keep an audit trail of actions in your application
|
73
|
+
test_files:
|
74
|
+
- spec/action_auditor_spec.rb
|
75
|
+
- spec/controllers/controller_integration_spec.rb
|
76
|
+
- spec/spec_helper.rb
|