acts_as_audited 1.0.1

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 ADDED
@@ -0,0 +1,4 @@
1
+ acts_as_audited_plugin.sqlite3.db
2
+ test/debug.log
3
+ coverage/
4
+ pkg
data/CHANGELOG ADDED
@@ -0,0 +1,25 @@
1
+ acts_as_audited ChangeLog
2
+ -------------------------------------------------------------------------------
3
+ * 2009-01-27 - Store old and new values for updates, and store all attributes on destroy.
4
+ Refactored revisioning methods to work as expected
5
+ * 2008-10-10 - changed to make it work in development mode
6
+ * 2008-04-19 - refactored to make compatible with dirty tracking in edge rails
7
+ and to stop storing both old and new values in a single audit
8
+ * 2008-04-18 - Fix NoMethodError when trying to access the :previous revision
9
+ on a model that doesn't have previous revisions [Alex Soto]
10
+ * 2008-03-21 - added #changed_attributes to get access to the changes before a
11
+ save [Chris Parker]
12
+ * 2007-12-16 - Added #revision_at for retrieving a revision from a specific
13
+ time [Jacob Atzen]
14
+ * 2007-12-16 - Fix error when getting revision from audit with no changes
15
+ [Geoffrey Wiseman]
16
+ * 2007-12-16 - Remove dependency on acts_as_list
17
+ * 2007-06-17 - Added support getting previous revisions
18
+ * 2006-11-17 - Replaced use of singleton User.current_user with cache sweeper
19
+ implementation for auditing the user that made the change
20
+ * 2006-11-17 - added migration generator
21
+ * 2006-08-14 - incorporated changes from Michael Schuerig to write_attribute
22
+ that saves the new value after every change and not just the
23
+ first, and performs proper type-casting before doing comparisons
24
+ * 2006-08-14 - The "changes" are now saved as a serialized hash
25
+ * 2006-07-21 - initial version
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright © 2008 Brandon Keepers - Collective Idea
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,84 @@
1
+ = acts_as_audited
2
+
3
+ acts_as_audited is an ActiveRecord extension that logs all changes to your models in an audits table.
4
+
5
+ The purpose of this fork is to store both the previous values and the changed value, making each audit record selfcontained.
6
+
7
+ == Installation
8
+
9
+ * acts_as_audited can be installed as a gem:
10
+
11
+ # config/environment.rb
12
+ config.gem 'collectiveidea-acts_as_audited', :lib => false,
13
+ :source => 'http://gems.github.com'
14
+
15
+ or a plugin:
16
+
17
+ script/plugin install git://github.com/collectiveidea/acts_as_audited.git
18
+
19
+ * Generate the migration
20
+ script/generate audited_migration add_audits_table
21
+ rake db:migrate
22
+
23
+ == Usage
24
+
25
+ If you're using acts_as_audited within Rails, you can simply declare which models should be audited. acts_as_audited can also automatically record the user that made the change if your controller has a <tt>current_user</tt> method.
26
+
27
+ class ApplicationController < ActionController::Base
28
+ audit User, List, Item => {:except => :password}
29
+ protected
30
+ def current_user
31
+ @user ||= User.find(session[:user])
32
+ end
33
+ end
34
+
35
+ To get auditing outside of Rails you can explicitly declare <tt>acts_as_audited</tt> on your models:
36
+
37
+ class User < ActiveRecord::Base
38
+ acts_as_audited :except => [:password, :mistress]
39
+ end
40
+
41
+ To record a user in the audits when the sweepers are not available, you can use <tt>as_user</tt>:
42
+
43
+ Audit.as_user( user ) do
44
+ # Perform changes on audited models
45
+ end
46
+
47
+ See http://opensoul.org/2006/07/21/acts_as_audited for more information.
48
+
49
+ == Caveats
50
+
51
+ If your model declares +attr_accessible+ after +acts_as_audited+, you need to set +:protect+ to false. acts_as_audited uses +attr_protected+ internally to prevent malicious users from unassociating your audits, and Rails does not allow both +attr_protected+ and +attr_accessible+. It will default to false if +attr_accessible+ is called before +acts_as_audited+, but needs to be explicitly set if it is called after.
52
+
53
+ class User < ActiveRecord::Base
54
+ acts_as_audited :protect => false
55
+ attr_accessible :name
56
+ end
57
+
58
+ === ActiveScaffold
59
+
60
+ Many users have also reported problems with acts_as_audited and ActiveScaffold, which appears to be caused by a limitation in ActiveScaffold not supporting polymorphic associations. To get it to work with ActiveScaffold:
61
+
62
+ class ApplicationController < ActionController::Base
63
+ audit MyModel, :only => [:create, :update, :destroy]
64
+ end
65
+
66
+ == Compatability
67
+
68
+ acts_as_audited works with Rails 2.1 or later.
69
+
70
+ == Contributing
71
+
72
+ Contributions are always welcome. Checkout the latest code on GitHub:
73
+ http://github.com/collectiveidea/acts_as_audited
74
+
75
+ Please include tests with your patches. There are a few gems required to run the tests:
76
+ $ gem install multi_rails
77
+ $ gem install thoughtbot-shoulda jnunemaker-matchy --source http://gems.github.com
78
+
79
+ Make sure the tests pass against all versions of Rails since 2.1:
80
+
81
+ $ rake test:multi_rails:all
82
+
83
+ Please report bugs or feature suggestions on GitHub:
84
+ http://github.com/collectiveidea/acts_as_audited/issues
data/Rakefile ADDED
@@ -0,0 +1,57 @@
1
+ require 'rake'
2
+ require 'load_multi_rails_rake_tasks'
3
+ require 'rake/testtask'
4
+ require 'rake/rdoctask'
5
+
6
+ desc 'Default: run tests.'
7
+ task :default => :test
8
+
9
+ begin
10
+ require 'jeweler'
11
+ Jeweler::Tasks.new do |gem|
12
+ gem.name = "acts_as_audited"
13
+ gem.summary = %Q{ActiveRecord extension that logs all changes to your models in an audits table}
14
+ gem.email = "brandon@opensoul.org"
15
+ gem.homepage = "http://github.com/collectiveidea/acts_as_audited"
16
+ gem.authors = ["Brandon Keepers"]
17
+ gem.add_dependency 'activerecord', '>=2.1'
18
+ gem.add_development_dependency "thoughtbot-shoulda"
19
+ gem.add_development_dependency "jnunemaker-matchy"
20
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
21
+ end
22
+ # Jeweler::GemcutterTasks.new
23
+ rescue LoadError
24
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
25
+ end
26
+
27
+ desc 'Test the acts_as_audited plugin'
28
+ Rake::TestTask.new(:test) do |t|
29
+ t.libs << 'lib'
30
+ t.pattern = 'test/**/*_test.rb'
31
+ t.verbose = true
32
+ end
33
+
34
+ task :test => :check_dependencies
35
+
36
+ begin
37
+ require 'rcov/rcovtask'
38
+ Rcov::RcovTask.new do |test|
39
+ test.libs << 'test'
40
+ test.pattern = 'test/**/*_test.rb'
41
+ test.verbose = true
42
+ test.rcov_opts = %w(--exclude test,/usr/lib/ruby,/Library/Ruby,$HOME/.gem --sort coverage)
43
+ end
44
+ rescue LoadError
45
+ task :rcov do
46
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
47
+ end
48
+ end
49
+
50
+ desc 'Generate documentation for the acts_as_audited plugin.'
51
+ Rake::RDocTask.new(:rdoc) do |rdoc|
52
+ rdoc.rdoc_dir = 'doc'
53
+ rdoc.title = 'acts_as_audited'
54
+ rdoc.options << '--line-numbers' << '--inline-source'
55
+ rdoc.rdoc_files.include('README')
56
+ rdoc.rdoc_files.include('lib/**/*.rb')
57
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.1
@@ -0,0 +1,72 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{acts_as_audited}
8
+ s.version = "1.0.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Brandon Keepers"]
12
+ s.date = %q{2009-09-28}
13
+ s.email = %q{brandon@opensoul.org}
14
+ s.extra_rdoc_files = [
15
+ "LICENSE",
16
+ "README"
17
+ ]
18
+ s.files = [
19
+ ".gitignore",
20
+ "CHANGELOG",
21
+ "LICENSE",
22
+ "README",
23
+ "Rakefile",
24
+ "VERSION",
25
+ "acts_as_audited.gemspec",
26
+ "generators/audited_migration/USAGE",
27
+ "generators/audited_migration/audited_migration_generator.rb",
28
+ "generators/audited_migration/templates/migration.rb",
29
+ "init.rb",
30
+ "lib/acts_as_audited.rb",
31
+ "lib/acts_as_audited/audit.rb",
32
+ "lib/acts_as_audited/audit_sweeper.rb",
33
+ "rails/init.rb",
34
+ "test/acts_as_audited_test.rb",
35
+ "test/audit_sweeper_test.rb",
36
+ "test/audit_test.rb",
37
+ "test/db/database.yml",
38
+ "test/db/schema.rb",
39
+ "test/test_helper.rb"
40
+ ]
41
+ s.homepage = %q{http://github.com/collectiveidea/acts_as_audited}
42
+ s.rdoc_options = ["--charset=UTF-8"]
43
+ s.require_paths = ["lib"]
44
+ s.rubygems_version = %q{1.3.5}
45
+ s.summary = %q{ActiveRecord extension that logs all changes to your models in an audits table}
46
+ s.test_files = [
47
+ "test/acts_as_audited_test.rb",
48
+ "test/audit_sweeper_test.rb",
49
+ "test/audit_test.rb",
50
+ "test/db/schema.rb",
51
+ "test/test_helper.rb"
52
+ ]
53
+
54
+ if s.respond_to? :specification_version then
55
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
56
+ s.specification_version = 3
57
+
58
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
59
+ s.add_runtime_dependency(%q<activerecord>, [">= 2.1"])
60
+ s.add_development_dependency(%q<thoughtbot-shoulda>, [">= 0"])
61
+ s.add_development_dependency(%q<jnunemaker-matchy>, [">= 0"])
62
+ else
63
+ s.add_dependency(%q<activerecord>, [">= 2.1"])
64
+ s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
65
+ s.add_dependency(%q<jnunemaker-matchy>, [">= 0"])
66
+ end
67
+ else
68
+ s.add_dependency(%q<activerecord>, [">= 2.1"])
69
+ s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
70
+ s.add_dependency(%q<jnunemaker-matchy>, [">= 0"])
71
+ end
72
+ end
@@ -0,0 +1,7 @@
1
+ Description:
2
+ The audited migration generator creates a migration to add the audits table.
3
+
4
+ Example:
5
+ ./script/generate audited_migration add_audits_table
6
+
7
+ This will create a migration in db/migrate/. Run "rake db:migrate" to update your database.
@@ -0,0 +1,7 @@
1
+ class AuditedMigrationGenerator < Rails::Generator::NamedBase
2
+ def manifest
3
+ record do |m|
4
+ m.migration_template 'migration.rb', 'db/migrate'
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,23 @@
1
+ class <%= class_name %> < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :audits, :force => true do |t|
4
+ t.column :auditable_id, :integer
5
+ t.column :auditable_type, :string
6
+ t.column :user_id, :integer
7
+ t.column :user_type, :string
8
+ t.column :username, :string
9
+ t.column :action, :string
10
+ t.column :changes, :text
11
+ t.column :version, :integer, :default => 0
12
+ t.column :created_at, :datetime
13
+ end
14
+
15
+ add_index :audits, [:auditable_id, :auditable_type], :name => 'auditable_index'
16
+ add_index :audits, [:user_id, :user_type], :name => 'user_index'
17
+ add_index :audits, :created_at
18
+ end
19
+
20
+ def self.down
21
+ drop_table :audits
22
+ end
23
+ end
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require File.join(File.dirname(__FILE__), 'rails', 'init')
@@ -0,0 +1,122 @@
1
+ require 'set'
2
+
3
+ # Audit saves the changes to ActiveRecord models. It has the following attributes:
4
+ #
5
+ # * <tt>auditable</tt>: the ActiveRecord model that was changed
6
+ # * <tt>user</tt>: the user that performed the change; a string or an ActiveRecord model
7
+ # * <tt>action</tt>: one of create, update, or delete
8
+ # * <tt>changes</tt>: a serialized hash of all the changes
9
+ # * <tt>created_at</tt>: Time that the change was performed
10
+ #
11
+ class Audit < ActiveRecord::Base
12
+ belongs_to :auditable, :polymorphic => true
13
+ belongs_to :user, :polymorphic => true
14
+
15
+ before_create :set_version_number, :set_audit_user
16
+
17
+ serialize :changes
18
+
19
+ cattr_accessor :audited_class_names
20
+ self.audited_class_names = Set.new
21
+
22
+ def self.audited_classes
23
+ self.audited_class_names.map(&:constantize)
24
+ end
25
+
26
+ cattr_accessor :audit_as_user
27
+ self.audit_as_user = nil
28
+
29
+ # All audits made during the block called will be recorded as made
30
+ # by +user+. This method is hopefully threadsafe, making it ideal
31
+ # for background operations that require audit information.
32
+ def self.as_user(user, &block)
33
+ Thread.current[:acts_as_audited_user] = user
34
+
35
+ yield
36
+
37
+ Thread.current[:acts_as_audited_user] = nil
38
+ end
39
+
40
+ # Allows user to be set to either a string or an ActiveRecord object
41
+ def user_as_string=(user) #:nodoc:
42
+ # reset both either way
43
+ self.user_as_model = self.username = nil
44
+ user.is_a?(ActiveRecord::Base) ?
45
+ self.user_as_model = user :
46
+ self.username = user
47
+ end
48
+ alias_method :user_as_model=, :user=
49
+ alias_method :user=, :user_as_string=
50
+
51
+ def user_as_string #:nodoc:
52
+ self.user_as_model || self.username
53
+ end
54
+ alias_method :user_as_model, :user
55
+ alias_method :user, :user_as_string
56
+
57
+ def revision
58
+ clazz = auditable_type.constantize
59
+ returning clazz.find_by_id(auditable_id) || clazz.new do |m|
60
+ Audit.assign_revision_attributes(m, self.class.reconstruct_attributes(ancestors).merge({:version => version}))
61
+ end
62
+ end
63
+
64
+ def ancestors
65
+ self.class.find(:all, :order => 'version',
66
+ :conditions => ['auditable_id = ? and auditable_type = ? and version <= ?',
67
+ auditable_id, auditable_type, version])
68
+ end
69
+
70
+ # Returns a hash of the changed attributes with the new values
71
+ def new_attributes
72
+ (changes || {}).inject({}.with_indifferent_access) do |attrs,(attr,values)|
73
+ attrs[attr] = Array(values).last
74
+ attrs
75
+ end
76
+ end
77
+
78
+ # Returns a hash of the changed attributes with the old values
79
+ def old_attributes
80
+ (changes || {}).inject({}.with_indifferent_access) do |attrs,(attr,values)|
81
+ attrs[attr] = Array(values).first
82
+ attrs
83
+ end
84
+ end
85
+
86
+ def self.reconstruct_attributes(audits)
87
+ attributes = {}
88
+ result = audits.collect do |audit|
89
+ attributes.merge!(audit.new_attributes).merge!(:version => audit.version)
90
+ yield attributes if block_given?
91
+ end
92
+ block_given? ? result : attributes
93
+ end
94
+
95
+ def self.assign_revision_attributes(record, attributes)
96
+ attributes.each do |attr, val|
97
+ if record.respond_to?("#{attr}=")
98
+ record.attributes.has_key?(attr.to_s) ?
99
+ record[attr] = val :
100
+ record.send("#{attr}=", val)
101
+ end
102
+ end
103
+ record
104
+ end
105
+
106
+ private
107
+
108
+ def set_version_number
109
+ max = self.class.maximum(:version,
110
+ :conditions => {
111
+ :auditable_id => auditable_id,
112
+ :auditable_type => auditable_type
113
+ }) || 0
114
+ self.version = max + 1
115
+ end
116
+
117
+ def set_audit_user
118
+ self.user = Thread.current[:acts_as_audited_user] if Thread.current[:acts_as_audited_user]
119
+ nil # prevent stopping callback chains
120
+ end
121
+
122
+ end
@@ -0,0 +1,79 @@
1
+
2
+ module CollectiveIdea #:nodoc:
3
+ module ActionController #:nodoc:
4
+ module Audited #:nodoc:
5
+
6
+ def self.included(base) # :nodoc:
7
+ base.extend ClassMethods
8
+ end
9
+
10
+ module ClassMethods
11
+ # Declare models that should be audited in your controller
12
+ #
13
+ # class ApplicationController < ActionController::Base
14
+ # audit User, Widget
15
+ # end
16
+ #
17
+ # You can optionally pass options for each model to be audited:
18
+ #
19
+ # audit User, Widget, Task => { :except => :position }
20
+ #
21
+ # NOTE: Models which do not have options must be listed first in the
22
+ # call to <tt>audit</tt>.
23
+ #
24
+ # See <tt>CollectiveIdea::Acts::Audited::ClassMethods#acts_as_audited</tt>
25
+ # for configuration options
26
+ #
27
+ # You can also specify an options hash which will be passed on to
28
+ # Rails' cache_sweeper call:
29
+ #
30
+ # audit User, :only => [:create, :edit, :destroy]
31
+ #
32
+ def audit(*models)
33
+ options = models.extract_options!
34
+
35
+ # Parse the options hash looking for classes
36
+ options.each_key do |key|
37
+ models << [key, options.delete(key)] if key.is_a?(Class)
38
+ end
39
+
40
+ models.each do |(model, model_options)|
41
+ model.send :acts_as_audited, model_options || {}
42
+
43
+ # disable ActiveRecord callbacks, which are replaced by the AuditSweeper
44
+ model.send :disable_auditing_callbacks
45
+
46
+ # prevent observer from being registered multiple times
47
+ model.delete_observer(AuditSweeper.instance)
48
+ model.add_observer(AuditSweeper.instance)
49
+ end
50
+
51
+ class_eval do
52
+ cache_sweeper :audit_sweeper, options
53
+ end
54
+ end
55
+ end
56
+
57
+ end
58
+ end
59
+ end
60
+
61
+ class AuditSweeper < ActionController::Caching::Sweeper #:nodoc:
62
+
63
+ def after_create(record)
64
+ record.send(:audit_create, current_user)
65
+ end
66
+
67
+ def after_destroy(record)
68
+ record.send(:audit_destroy, current_user)
69
+ end
70
+
71
+ def before_update(record)
72
+ record.send(:audit_update, current_user)
73
+ end
74
+
75
+ def current_user
76
+ controller.send :current_user if controller.respond_to?(:current_user, true)
77
+ end
78
+
79
+ end