auditor 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Near Infinity Corporation
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,69 @@
1
+ = Auditor
2
+
3
+ Auditor is a Rails 3 plugin for auditing access to your ActiveRecord model objects. It allows you to declaratively specify what CRUD operations should be audited and store that audit data in the database. You can also specify what attributes of model objects should automatically be audited and which ones should be ignored.
4
+
5
+ To audit your model objects you must specify which operations should be audited and which model attributes should be tracked. This "specify what you want to collect" approach avoids being overwhelmed with data and makes you carefully consider what is most important to audit.
6
+
7
+ = Installation
8
+
9
+ To use it with your Rails 3 project, add the following line to your Gemfile
10
+
11
+ gem 'auditor'
12
+
13
+ Auditor can also be installed as a Rails plugin
14
+
15
+ rails plugin install git://github.com/nearinfinity/auditor.git
16
+
17
+ Generate the migration and create the audits table
18
+
19
+ rails generate auditor:migration
20
+ rake db:migrate
21
+
22
+ = Setup
23
+
24
+ Auditor needs to know who the current user is, but with no standard for doing so you'll have to do a little work to set things up. You simply need to set your current user model object as the Auditor current user before any CRUD operations are performed. For example, in a Rails application you could add the following to your application_controller.rb
25
+
26
+ class ApplicationController < ActionController::Base
27
+ before_filter :set_current_user
28
+
29
+ private
30
+
31
+ def set_current_user
32
+ Auditor::User.current_user = @current_user
33
+ end
34
+ end
35
+
36
+ = Examples
37
+
38
+ Auditor works very similarly to Joshua Clayton's acts_as_auditable plugin. There are two audit calls in the example below. The first declares that create and update actions should be audited for the EditablePage model and the string returned by the passed block should be included as a custom message. The second audit call simply changes the custom message when auditing destroy (aka delete) actions.
39
+
40
+ class EditablePage < ActiveRecord::Base
41
+ include Auditor::ModelAudit
42
+
43
+ has_many :tags
44
+
45
+ audit(:create, :update) { |model, user| "Editable page modified by #{user.display_name}" }
46
+ audit(:destroy) { |model, user| "#{user.display_name} deleted editable page #{model.id}" }
47
+ end
48
+
49
+ All audit data is stored in a table named Audits, which is automatically created for you when you run the migration included with the plugin. However, there's a lot more recorded than just the custom message, including:
50
+
51
+ * auditable_id - the primary key of the table belonging to the audited model object
52
+ * auditable_type - the class type of the audited model object
53
+ * auditable_version - the version number of the audited model object (if versioning is tracked)
54
+ * user_id - the primary key of the table belonging to the user being audited
55
+ * user_type - the class type of the model object representing users in your application
56
+ * action - a string indicating the action that was audited (create, update, destroy, or find)
57
+ * message - the custom message returned by any block passed to the audit call
58
+ * edits - a YAML string containing the before and after state of any model attributes that changed
59
+ * created_at - the date and time the audit record was recorded
60
+
61
+ The edits column automatically serializes the before and after state of any model attributes that change during the action. If there are only a few attributes you want to audit or a couple that you want to prevent from being audited, you can specify that in the audit call. For example
62
+
63
+ # Prevent SSN and passwords from being saved in the audit table
64
+ audit(:create, :destroy, :except => [:ssn, :password])
65
+
66
+ # Only audit edits to the title column when destroying/deleting
67
+ audit(:destroy, :only => :title)
68
+
69
+ Copyright (c) 2011 Near Infinity Corporation, released under the MIT license
data/lib/auditor.rb ADDED
@@ -0,0 +1,9 @@
1
+ require 'auditor/audit'
2
+ require 'auditor/integration'
3
+ require 'auditor/model_audit'
4
+ require 'auditor/user'
5
+ require 'auditor/version'
6
+
7
+ module Auditor
8
+ class Error < StandardError; end
9
+ end
@@ -0,0 +1,6 @@
1
+ require 'active_record'
2
+
3
+ class Audit < ActiveRecord::Base
4
+ validates_presence_of :auditable_id, :auditable_type, :user_id, :user_type, :action
5
+ serialize :edits
6
+ end
@@ -0,0 +1,36 @@
1
+ module Auditor
2
+ class ConfigParser
3
+
4
+ def self.extract_config(args)
5
+ options = (args.delete_at(args.size - 1) if args.last.kind_of?(Hash)) || {}
6
+ normalize_config args, options
7
+ validate_config args, options
8
+ options = normalize_options(options)
9
+
10
+ [args, options]
11
+ end
12
+
13
+ private
14
+
15
+ def self.normalize_config(actions, options)
16
+ actions.each_with_index { |item, index| actions[index] = item.to_sym }
17
+ options.each_pair { |k, v| options[k.to_sym] = options.delete(k) unless k.kind_of? Symbol }
18
+ end
19
+
20
+ def self.normalize_options(options)
21
+ return { :except => [], :only => [] } if options.nil? || options.empty?
22
+ options[:except] = options[:except] || []
23
+ options[:only] = options[:only] || []
24
+ options[:except] = Array(options[:except]).map(&:to_s)
25
+ options[:only] = Array(options[:only]).map(&:to_s)
26
+ options
27
+ end
28
+
29
+ def self.validate_config(actions, options)
30
+ raise Auditor::Error.new "at least one :create, :find, :update, or :destroy action must be specified" if actions.empty?
31
+ raise Auditor::Error.new ":create, :find, :update, and :destroy are the only valid actions" unless actions.all? { |a| [:create, :find, :update, :destroy].include? a }
32
+ raise Auditor::Error.new "only one of :except and :only can be specified" if options.size > 1
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,49 @@
1
+ require 'auditor/thread_status'
2
+
3
+ module Auditor
4
+ module Integration
5
+
6
+ def without_auditor
7
+ previously_disabled = auditor_disabled?
8
+ disable_auditor
9
+
10
+ begin
11
+ result = yield if block_given?
12
+ ensure
13
+ enable_auditor unless previously_disabled
14
+ end
15
+
16
+ result
17
+ end
18
+
19
+ def with_auditor
20
+ previously_disabled = auditor_disabled?
21
+ enable_auditor
22
+
23
+ begin
24
+ result = yield if block_given?
25
+ ensure
26
+ disable_auditor if previously_disabled
27
+ end
28
+
29
+ result
30
+ end
31
+
32
+ def disable_auditor
33
+ Auditor::ThreadStatus.disable
34
+ end
35
+
36
+ def enable_auditor
37
+ Auditor::ThreadStatus.enable
38
+ end
39
+
40
+ def auditor_disabled?
41
+ Auditor::ThreadStatus.disabled?
42
+ end
43
+
44
+ def auditor_enabled?
45
+ Auditor::ThreadStatus.enabled?
46
+ end
47
+
48
+ end
49
+ end
@@ -0,0 +1,47 @@
1
+ require 'auditor/thread_status'
2
+ require 'auditor/config_parser'
3
+ require 'auditor/recorder'
4
+
5
+ module Auditor
6
+ module ModelAudit
7
+
8
+ def self.included(base)
9
+ base.extend ClassMethods
10
+ end
11
+
12
+ # ActiveRecord won't call the after_find handler unless it see's a specific after_find method defined
13
+ def after_find; end
14
+
15
+ def auditor_disabled?
16
+ Auditor::ThreadStatus.disabled? || @auditor_disabled
17
+ end
18
+
19
+ module ClassMethods
20
+ def audit(*args, &blk)
21
+ actions, options = Auditor::ConfigParser.extract_config(args)
22
+
23
+ actions.each do |action|
24
+ unless action.to_sym == :find
25
+ callback = "auditor_before_#{action}"
26
+ define_method(callback) do
27
+ @auditor_auditor = Auditor::Recorder.new(action, self, options, &blk)
28
+ @auditor_auditor.audit_before unless auditor_disabled?
29
+ true
30
+ end
31
+ send "before_#{action}".to_sym, callback
32
+ end
33
+
34
+ callback = "auditor_after_#{action}"
35
+ define_method(callback) do
36
+ @auditor_auditor = Auditor::Recorder.new(action, self, options, &blk) if action.to_sym == :find
37
+ @auditor_auditor.audit_after unless auditor_disabled?
38
+ true
39
+ end
40
+ send "after_#{action}".to_sym, callback
41
+
42
+ end
43
+ end
44
+ end
45
+
46
+ end
47
+ end
@@ -0,0 +1,44 @@
1
+ require 'auditor/user'
2
+
3
+ module Auditor
4
+ class Recorder
5
+
6
+ def initialize(action, model, options, &blk)
7
+ @action, @model, @options, @blk = action.to_sym, model, options, blk
8
+ end
9
+
10
+ def audit_before
11
+ @audit = Audit.new(:edits => prepare_edits(@model.changes, @options))
12
+ end
13
+
14
+ def audit_after
15
+ @audit ||= Audit.new
16
+
17
+ @audit.attributes = {
18
+ :auditable_id => @model.id,
19
+ :auditable_type => @model.class.to_s,
20
+ :user_id => user.id,
21
+ :user_type => user.class.to_s,
22
+ :action => @action.to_s
23
+ }
24
+
25
+ @audit.auditable_version = @model.version if @model.respond_to? :version
26
+ @audit.message = @blk.call(@model, user) if @blk
27
+
28
+ @audit.save
29
+ end
30
+
31
+ private
32
+ def user
33
+ Auditor::User.current_user
34
+ end
35
+
36
+ def prepare_edits(changes, options)
37
+ chg = changes.dup
38
+ chg = chg.delete_if { |key, value| options[:except].include? key } unless options[:except].empty?
39
+ chg = chg.delete_if { |key, value| !options[:only].include? key } unless options[:only].empty?
40
+ chg.empty? ? nil : chg
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,20 @@
1
+ module Auditor
2
+ module SpecHelpers
3
+ include Auditor::Integration
4
+
5
+ def self.included(base)
6
+ base.class_eval do
7
+ before(:each) do
8
+ disable_auditor
9
+ end
10
+
11
+ after(:each) do
12
+ enable_auditor
13
+ end
14
+ end
15
+ end
16
+
17
+ end
18
+ end
19
+
20
+
@@ -0,0 +1,18 @@
1
+ module Auditor
2
+ class ThreadLocal
3
+
4
+ def initialize(initial_value)
5
+ @thread_symbol = "#{rand}#{Time.now.to_f}"
6
+ set initial_value
7
+ end
8
+
9
+ def set(value)
10
+ Thread.current[@thread_symbol] = value
11
+ end
12
+
13
+ def get
14
+ Thread.current[@thread_symbol]
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,34 @@
1
+ require 'auditor/thread_local'
2
+
3
+ module Auditor
4
+ module ThreadStatus
5
+
6
+ def self.enabled?
7
+ status
8
+ end
9
+
10
+ def self.disabled?
11
+ !status
12
+ end
13
+
14
+ def self.enable
15
+ set_status true
16
+ end
17
+
18
+ def self.disable
19
+ set_status false
20
+ end
21
+
22
+ private
23
+ def self.status
24
+ @status = Auditor::ThreadLocal.new(true) if @status.nil?
25
+ @status.get
26
+ end
27
+
28
+ def self.set_status(status)
29
+ @status = Auditor::ThreadLocal.new(true) if @status.nil?
30
+ @status.set status
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,16 @@
1
+ module Auditor
2
+ module User
3
+ def current_user
4
+ Thread.current[@@current_user_symbol]
5
+ end
6
+
7
+ def current_user=(user)
8
+ Thread.current[@@current_user_symbol] = user
9
+ end
10
+
11
+ module_function :current_user, :current_user=
12
+
13
+ private
14
+ @@current_user_symbol = :auditor_current_user
15
+ end
16
+ end
@@ -0,0 +1,3 @@
1
+ module Auditor
2
+ VERSION = "1.0.0"
3
+ end
@@ -0,0 +1,9 @@
1
+ module Auditor
2
+ module Generators
3
+ class Base < Rails::Generators::NamedBase
4
+ def self.source_root
5
+ File.expand_path(File.join(File.dirname(__FILE__), 'auditor', generator_name, 'templates'))
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,26 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/migration'
3
+
4
+ module Auditor
5
+ module Generators
6
+ class MigrationGenerator < Rails::Generators::Base
7
+ include Rails::Generators::Migration
8
+
9
+ desc "Create migration for Auditor audits table"
10
+
11
+ source_root File.expand_path("../templates", __FILE__)
12
+
13
+ def self.next_migration_number(dirname)
14
+ if ActiveRecord::Base.timestamped_migrations
15
+ Time.now.utc.strftime("%Y%m%d%H%M%S")
16
+ else
17
+ "%.3d" % (current_migration_number(dirname) + 1)
18
+ end
19
+ end
20
+
21
+ def create_migration_file
22
+ migration_template 'migration.rb', 'db/migrate/create_audits_table.rb'
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,19 @@
1
+ class CreateAuditsTable < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :audits, :force => true do |t|
4
+ t.column :auditable_id, :integer, :null => false
5
+ t.column :auditable_type, :string, :null => false
6
+ t.column :auditable_version, :integer
7
+ t.column :user_id, :integer, :null => false
8
+ t.column :user_type, :string, :null => false
9
+ t.column :action, :string, :null => false
10
+ t.column :message, :text
11
+ t.column :edits, :text
12
+ t.column :created_at, :datetime, :null => false
13
+ end
14
+ end
15
+
16
+ def self.down
17
+ drop_table :audits
18
+ end
19
+ end
@@ -0,0 +1,53 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+ require 'auditor'
3
+
4
+ describe Auditor::ConfigParser do
5
+
6
+ describe 'Configuration' do
7
+ it "should parse actions and options from a config array" do
8
+ config = Auditor::ConfigParser.extract_config([:create, 'update', {:only => :username}])
9
+ config.should_not be_nil
10
+ config.should have(2).items
11
+ config[0].should =~ [:create, :update]
12
+ config[1].should == {:only => ["username"], :except => []}
13
+ end
14
+
15
+ it "should parse actions and options from a config array when options are absent" do
16
+ config = Auditor::ConfigParser.extract_config([:create, 'update'])
17
+ config.should_not be_nil
18
+ config.should have(2).items
19
+ config[0].should =~ [:create, :update]
20
+ config[1].should == {:only => [], :except => []}
21
+ end
22
+
23
+ it "should parse actions" do
24
+ config = Auditor::ConfigParser.extract_config([:create])
25
+ config.should_not be_nil
26
+ config.should have(2).items
27
+ config[0].should =~ [:create]
28
+ config[1].should == {:only => [], :except => []}
29
+ end
30
+
31
+ end
32
+
33
+ describe 'Configuration Validation' do
34
+ it "should raise a Auditor::Error if no action is specified" do
35
+ lambda {
36
+ Auditor::ConfigParser.instance_eval { validate_config([], {}) }
37
+ }.should raise_error(Auditor::Error)
38
+ end
39
+
40
+ it "should raise a Auditor::Error if an invalid action is specified" do
41
+ lambda {
42
+ Auditor::ConfigParser.instance_eval { validate_config([:create, :udate], {}) }
43
+ }.should raise_error(Auditor::Error)
44
+ end
45
+
46
+ it "should raise a Auditor::Error if both the except and only options are specified" do
47
+ lambda {
48
+ Auditor::ConfigParser.instance_eval { validate_config([:find], {:except => :ssn, :only => :username}) }
49
+ }.should raise_error(Auditor::Error)
50
+ end
51
+ end
52
+
53
+ end
@@ -0,0 +1,83 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+ require 'auditor'
3
+
4
+ describe Auditor::ModelAudit do
5
+
6
+ before(:each) do
7
+ Auditor::User.current_user = (Class.new do
8
+ def id; 1 end
9
+ end).new
10
+ end
11
+
12
+ it 'should audit find' do
13
+ c = new_model_class.instance_eval { audit(:find); self }
14
+ verify_standard_audits(c.new, :find)
15
+ end
16
+
17
+ it 'should audit create' do
18
+ c = new_model_class.instance_eval { audit(:create); self }
19
+ verify_standard_audits(c.new, :create)
20
+ end
21
+
22
+ it 'should audit update' do
23
+ c = new_model_class.instance_eval { audit(:update); self }
24
+ verify_standard_audits(c.new, :update)
25
+ end
26
+
27
+ it 'should audit destroy' do
28
+ c = new_model_class.instance_eval { audit(:destroy); self }
29
+ verify_standard_audits(c.new, :destroy)
30
+ end
31
+
32
+ it 'should allow multiple actions to be specified with one audit statment' do
33
+ c = new_model_class.instance_eval { audit(:create, :update); self }
34
+ verify_standard_audits(c.new, :create, :update)
35
+
36
+ c = new_model_class.instance_eval { audit(:create, :update, :destroy); self }
37
+ verify_standard_audits(c.new, :create, :update, :destroy)
38
+
39
+ c = new_model_class.instance_eval { audit(:create, :update, :destroy, :find); self }
40
+ verify_standard_audits(c.new, :create, :update, :destroy, :find)
41
+ end
42
+
43
+ def verify_standard_audits(instance, *audited_callbacks)
44
+ audited_callbacks.each do |action|
45
+ mock_auditor = mock('auditor')
46
+ Auditor::Recorder.should_receive(:new).and_return(mock_auditor)
47
+ mock_auditor.should_receive(:audit_before) unless action == :find
48
+ mock_auditor.should_receive(:audit_after)
49
+ instance.send action
50
+ end
51
+ end
52
+
53
+ def new_model_class
54
+ Class.new(ActiveRecordMock) do
55
+ include Auditor::ModelAudit
56
+ end
57
+ end
58
+
59
+ class ActiveRecordMock
60
+ def id; 1 end
61
+
62
+ [:create, :update, :destroy, :find].each do |action|
63
+ define_method(action) do
64
+ send "before_#{action}".to_sym unless action == :find
65
+ send "after_#{action}".to_sym
66
+ end
67
+
68
+ metaclass = class << self; self end
69
+ metaclass.instance_eval do
70
+ unless action == :find
71
+ define_method("before_#{action}") do |method|
72
+ define_method("before_#{action}") { send method }
73
+ end
74
+ end
75
+ define_method("after_#{action}") do |method|
76
+ define_method("after_#{action}") { send method }
77
+ end
78
+ end
79
+ end
80
+
81
+ end
82
+
83
+ end
@@ -0,0 +1,120 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+ require 'auditor'
3
+
4
+ describe Auditor::Recorder do
5
+ before(:each) do
6
+ @user = Auditor::User.current_user = new_model(7)
7
+ end
8
+
9
+ it 'should create and save a new audit record' do
10
+ model = new_model(42, {'first_name' => ['old','new']})
11
+
12
+ auditor = Auditor::Recorder.new(:create, model, {:except => [], :only => []}) { |m, u| "Model: #{m.id} User: #{u.id}" }
13
+
14
+ audit = do_callbacks(auditor, model)
15
+
16
+ audit.saved.should be_true
17
+ audit.action.should == 'create'
18
+ audit.edits.to_a.should =~ [['first_name', ['old', 'new']]]
19
+ audit.auditable_id.should == 42
20
+ audit.auditable_type.should == model.class.to_s
21
+ audit.user_id.should == @user.id
22
+ audit.user_type.should == @user.class.to_s
23
+ audit.auditable_version.should be_nil
24
+ audit.message.should == 'Model: 42 User: 7'
25
+ end
26
+
27
+ it 'should capture the new id of a created record' do
28
+ model = new_model
29
+
30
+ auditor = Auditor::Recorder.new(:create, model, {:except => [], :only => []})
31
+
32
+ audit = do_callbacks(auditor, model)
33
+
34
+ audit.saved.should be_true
35
+ audit.auditable_id.should == 42
36
+ audit.auditable_type.should == model.class.to_s
37
+ end
38
+
39
+ it 'should set message details to nil if they are not given' do
40
+ model = new_model
41
+ auditor = Auditor::Recorder.new(:create, model, {:except => [], :only => []})
42
+
43
+ audit = do_callbacks(auditor, model)
44
+
45
+ audit.saved.should be_true
46
+ audit.message.should be_nil
47
+ end
48
+
49
+ it 'should not save change details for excepted attributes' do
50
+ model = new_model(42, {'first_name' => ['old','new'], 'last_name' => ['old','new']})
51
+
52
+ auditor = Auditor::Recorder.new(:create, model, {:except => ['last_name'], :only => []})
53
+
54
+ audit = do_callbacks(auditor, model)
55
+
56
+ audit.saved.should be_true
57
+ audit.edits.to_a.should =~ [['first_name', ['old', 'new']]]
58
+ end
59
+
60
+ it 'should only save change details for onlyed attributes' do
61
+ model = new_model(42, {'first_name' => ['old','new'], 'last_name' => ['old','new']})
62
+
63
+ auditor = Auditor::Recorder.new(:create, model, {:except => [], :only => ['last_name']})
64
+
65
+ audit = do_callbacks(auditor, model)
66
+
67
+ audit.saved.should be_true
68
+ audit.edits.to_a.should =~ [['last_name', ['old', 'new']]]
69
+ end
70
+
71
+ it 'should not save attributes listed in both the only and except options' do
72
+ model = new_model(42, {'first_name' => ['old','new'], 'last_name' => ['old','new']})
73
+
74
+ auditor = Auditor::Recorder.new(:create, model, {:except => ['last_name'], :only => ['last_name']})
75
+
76
+ audit = do_callbacks(auditor, model)
77
+
78
+ audit.saved.should be_true
79
+ audit.edits.should be_nil
80
+ end
81
+
82
+ def do_callbacks(auditor, model)
83
+ auditor.audit_before
84
+ audit = auditor.instance_variable_get(:@audit)
85
+ audit.saved.should be_false
86
+
87
+ model.changes = nil
88
+ model.id = 42 if model.id.nil?
89
+
90
+ auditor.audit_after
91
+ auditor.instance_variable_get(:@audit)
92
+ end
93
+
94
+ def new_model(id = nil, changes = {})
95
+ model = (Class.new do; attr_accessor :id, :changes; end).new
96
+ model.id, model.changes = id, changes
97
+ model
98
+ end
99
+
100
+ class Audit
101
+ attr_accessor :edits, :saved
102
+ attr_accessor :action, :auditable_id, :auditable_type, :user_id, :user_type, :auditable_version, :message
103
+
104
+ def initialize(attrs={})
105
+ @edits = attrs.delete(:edits)
106
+ @saved = false
107
+ raise "You can only set the edits field in this class" unless attrs.empty?
108
+ end
109
+
110
+ def attributes=(attrs={})
111
+ attrs.each_pair do |key, val|
112
+ self.send("#{key}=".to_sym, val)
113
+ end
114
+ end
115
+
116
+ def save
117
+ @saved = true
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,45 @@
1
+ require 'rspec'
2
+
3
+ # Requires supporting files with custom matchers and macros, etc,
4
+ # in ./support/ and its subdirectories.
5
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
6
+
7
+ RSpec.configure do |config|
8
+ # If you're not using ActiveRecord you should remove these
9
+ # lines, delete config/database.yml and disable :active_record
10
+ # in your config/boot.rb
11
+ # config.use_transactional_fixtures = true
12
+ # config.use_instantiated_fixtures = false
13
+
14
+ # == Fixtures
15
+ #
16
+ # You can declare fixtures for each example_group like this:
17
+ # describe "...." do
18
+ # fixtures :table_a, :table_b
19
+ #
20
+ # Alternatively, if you prefer to declare them only once, you can
21
+ # do so right here. Just uncomment the next line and replace the fixture
22
+ # names with your fixtures.
23
+ #
24
+ # config.global_fixtures = :all
25
+ #
26
+ # If you declare global fixtures, be aware that they will be declared
27
+ # for all of your examples, even those that don't use them.
28
+ #
29
+ # You can also declare which fixtures to use (for example fixtures for test/fixtures):
30
+ #
31
+ # config.fixture_path = RAILS_ROOT + '/spec/fixtures/'
32
+ #
33
+ # == Mock Framework
34
+ #
35
+ # RSpec uses it's own mocking framework by default. If you prefer to
36
+ # use mocha, flexmock or RR, uncomment the appropriate line:
37
+ #
38
+ # config.mock_with :mocha
39
+ # config.mock_with :flexmock
40
+ # config.mock_with :rr
41
+ #
42
+ # == Notes
43
+ #
44
+ # For more information take a look at Spec::Runner::Configuration and Spec::Runner
45
+ end
@@ -0,0 +1,29 @@
1
+ module AuditorHelpers
2
+
3
+ def current_user=(user)
4
+ Auditor::User.current_user = user
5
+ end
6
+
7
+ def current_user
8
+ Auditor::User.current_user
9
+ end
10
+
11
+ def clear_current_user
12
+ Auditor::User.current_user = nil
13
+ end
14
+
15
+ def verify_audit(audit, audited, user, action, edits_nil=false, message_nil=true)
16
+ audit.auditable_id.should == audited.id
17
+ audit.auditable_type.should == audited.class.name
18
+ audit.user_id.should == user.id
19
+ audit.user_type.should == user.class.name
20
+ audit.action.should == action.to_s
21
+ audit.message.should be_nil if message_nil
22
+ audit.message.should_not be_nil unless message_nil
23
+ audit.edits.should be_nil if edits_nil
24
+ audit.edits.should_not be_nil unless edits_nil
25
+ end
26
+
27
+ end
28
+
29
+
@@ -0,0 +1,14 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+ require 'auditor/thread_local'
3
+
4
+ describe Auditor::ThreadLocal do
5
+ it "should properly set and get thread-local variables" do
6
+ val = "val"
7
+ tl = Auditor::ThreadLocal.new(val)
8
+ tl.get.should == val
9
+
10
+ val2 = "val2"
11
+ tl.set val2
12
+ tl.get.should == val2
13
+ end
14
+ end
@@ -0,0 +1,16 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+ require 'auditor'
3
+
4
+ describe Auditor::ThreadStatus do
5
+ it "should be enabled if set to enabled" do
6
+ Auditor::ThreadStatus.enable
7
+ Auditor::ThreadStatus.should be_enabled
8
+ Auditor::ThreadStatus.should_not be_disabled
9
+ end
10
+
11
+ it "should be disabled if set to disabled" do
12
+ Auditor::ThreadStatus.disable
13
+ Auditor::ThreadStatus.should_not be_enabled
14
+ Auditor::ThreadStatus.should be_disabled
15
+ end
16
+ end
data/spec/user_spec.rb ADDED
@@ -0,0 +1,25 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+ require 'auditor/user'
3
+
4
+ describe Auditor::User do
5
+ it "should return the same user that's set on the same thread" do
6
+ user = "user"
7
+ Auditor::User.current_user = user
8
+ Auditor::User.current_user.should == user
9
+ end
10
+
11
+ it "should not return the same user from a different thread" do
12
+ user = "user"
13
+ user2 = "user2"
14
+
15
+ Auditor::User.current_user = user
16
+
17
+ Thread.new do
18
+ Auditor::User.current_user.should be_nil
19
+ Auditor::User.current_user = user2
20
+ Auditor::User.current_user.should == user2
21
+ end
22
+
23
+ Auditor::User.current_user.should == user
24
+ end
25
+ end
metadata ADDED
@@ -0,0 +1,113 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: auditor
3
+ version: !ruby/object:Gem::Version
4
+ hash: 23
5
+ prerelease: false
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 0
10
+ version: 1.0.0
11
+ platform: ruby
12
+ authors:
13
+ - Jeff Kunkle
14
+ - Matt Wizeman
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2011-01-06 00:00:00 -05:00
20
+ default_executable:
21
+ dependencies:
22
+ - !ruby/object:Gem::Dependency
23
+ name: rspec
24
+ prerelease: false
25
+ requirement: &id001 !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ">="
29
+ - !ruby/object:Gem::Version
30
+ hash: 3
31
+ segments:
32
+ - 0
33
+ version: "0"
34
+ type: :development
35
+ version_requirements: *id001
36
+ description: Auditor allows you to declaratively specify what CRUD operations should be audited and save the audit data to the database.
37
+ email:
38
+ executables: []
39
+
40
+ extensions: []
41
+
42
+ extra_rdoc_files: []
43
+
44
+ files:
45
+ - lib/auditor/audit.rb
46
+ - lib/auditor/config_parser.rb
47
+ - lib/auditor/integration.rb
48
+ - lib/auditor/model_audit.rb
49
+ - lib/auditor/recorder.rb
50
+ - lib/auditor/spec_helpers.rb
51
+ - lib/auditor/thread_local.rb
52
+ - lib/auditor/thread_status.rb
53
+ - lib/auditor/user.rb
54
+ - lib/auditor/version.rb
55
+ - lib/auditor.rb
56
+ - lib/generators/auditor/migration/migration_generator.rb
57
+ - lib/generators/auditor/migration/templates/migration.rb
58
+ - lib/generators/auditor.rb
59
+ - LICENSE
60
+ - README.rdoc
61
+ - spec/config_parser_spec.rb
62
+ - spec/model_audit_spec.rb
63
+ - spec/recorder_spec.rb
64
+ - spec/spec_helper.rb
65
+ - spec/support/auditor_helpers.rb
66
+ - spec/thread_local_spec.rb
67
+ - spec/thread_status_spec.rb
68
+ - spec/user_spec.rb
69
+ has_rdoc: true
70
+ homepage: http://github.com/nearinfinity/auditor
71
+ licenses:
72
+ - MIT
73
+ post_install_message:
74
+ rdoc_options: []
75
+
76
+ require_paths:
77
+ - lib
78
+ required_ruby_version: !ruby/object:Gem::Requirement
79
+ none: false
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ hash: 3
84
+ segments:
85
+ - 0
86
+ version: "0"
87
+ required_rubygems_version: !ruby/object:Gem::Requirement
88
+ none: false
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ hash: 23
93
+ segments:
94
+ - 1
95
+ - 3
96
+ - 6
97
+ version: 1.3.6
98
+ requirements: []
99
+
100
+ rubyforge_project:
101
+ rubygems_version: 1.3.7
102
+ signing_key:
103
+ specification_version: 3
104
+ summary: Rails 3 plugin for auditing access to your ActiveRecord model objects
105
+ test_files:
106
+ - spec/config_parser_spec.rb
107
+ - spec/model_audit_spec.rb
108
+ - spec/recorder_spec.rb
109
+ - spec/spec_helper.rb
110
+ - spec/support/auditor_helpers.rb
111
+ - spec/thread_local_spec.rb
112
+ - spec/thread_status_spec.rb
113
+ - spec/user_spec.rb