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