Skip to content

Commit

Permalink
Merged acts_as_audited with master
Browse files Browse the repository at this point in the history
  • Loading branch information
David Davis committed Aug 11, 2009
2 parents ef2e5f4 + 02e35ac commit 88ef2b1
Show file tree
Hide file tree
Showing 16 changed files with 714 additions and 541 deletions.
5 changes: 5 additions & 0 deletions .gitignore
@@ -1,4 +1,5 @@
acts_as_audited_plugin.sqlite3.db
<<<<<<< HEAD:.gitignore
spec/debug.log
coverage/
.svn/entries
Expand All @@ -19,3 +20,7 @@ tasks/.svn/entries
tasks/.svn/format
.svn/dir-props
*svn*
=======
test/debug.log
coverage/
>>>>>>> 02e35ac368f3125887c4752a39610f5e914ff6b7:.gitignore
20 changes: 17 additions & 3 deletions README
Expand Up @@ -43,6 +43,12 @@ To get auditing outside of Rails you can explicitly declare <tt>acts_as_audited<
acts_as_audited :except => [:password, :mistress], include => [:items, :lists]
end

To record a user in the audits when the sweepers are not available, you can use <tt>as_user</tt>:

Audit.as_user( user ) do
# Perform changes on audited models
end

See http://opensoul.org/2006/07/21/acts_as_audited for more information.

== Caveats
Expand All @@ -69,7 +75,15 @@ acts_as_audited works with Rails 2.1 or later.
== Contributing

Contributions are always welcome. Checkout the latest code on GitHub:
http://github.com/d5/acts_as_audited
http://github.com/collectiveidea/acts_as_audited

Please include tests with your patches. There are a few gems required to run the tests:
$ gem install multi_rails
$ gem install thoughtbot-shoulda jnunemaker-matchy --source http://gems.github.com

Make sure the tests pass against all versions of Rails since 2.1:

$ rake test:multi_rails:all

Please report bugs or feature suggestions at:
http://collectiveidea.lighthouseapp.com/projects/20257-acts_as_audited
Please report bugs or feature suggestions on GitHub:
http://github.com/collectiveidea/acts_as_audited/issues
11 changes: 6 additions & 5 deletions Rakefile
@@ -1,14 +1,15 @@
require 'rake'
require 'spec/rake/spectask'
require 'load_multi_rails_rake_tasks'
require 'rake/testtask'
require 'rake/rdoctask'

desc 'Default: run specs.'
task :default => :spec
desc 'Default: run tests.'
task :default => :test

desc 'Test the acts_as_audited plugin'
Spec::Rake::SpecTask.new(:spec) do |t|
Rake::TestTask.new(:test) do |t|
t.libs << 'lib'
t.pattern = 'spec/**/*_spec.rb'
t.pattern = 'test/**/*_test.rb'
t.verbose = true
end

Expand Down
90 changes: 50 additions & 40 deletions lib/acts_as_audited.rb
@@ -1,16 +1,16 @@
# Copyright (c) 2006 Brandon Keepers
#
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
Expand Down Expand Up @@ -40,11 +40,12 @@ def self.included(base) # :nodoc:
module ClassMethods
# == Configuration options
#
#
# * +only+ - Only audit the given attributes
# * +except+ - Excludes fields from being saved in the audit log.
# By default, acts_as_audited will audit all but these fields:
#
# [self.primary_key, inheritance_column, 'lock_version', 'created_at', 'updated_at']
# By default, acts_as_audited will audit all but these fields:
#
# [self.primary_key, inheritance_column, 'lock_version', 'created_at', 'updated_at']
# You can add to those by passing one or an array of fields to skip.
#
# class User < ActiveRecord::Base
Expand All @@ -59,20 +60,23 @@ module ClassMethods
# acts_as_audited :protect => false
# attr_accessible :name
# end
#
#
def acts_as_audited(options = {})
# don't allow multiple calls
return if self.included_modules.include?(CollectiveIdea::Acts::Audited::InstanceMethods)

options = {:protect => accessible_attributes.nil?}.merge(options)

class_inheritable_reader :non_audited_columns
class_inheritable_reader :auditing_enabled
class_inheritable_reader :audit_associations

# except fields
except = [self.primary_key, inheritance_column, 'lock_version', 'created_at', 'updated_at']
except |= Array(options[:except]).collect(&:to_s) if options[:except]
if options[:only]
except = self.column_names - options[:only].flatten.map(&:to_s)
else
except = [self.primary_key, inheritance_column, 'lock_version', 'created_at', 'updated_at']
except |= Array(options[:except]).collect(&:to_s) if options[:except]
end
write_inheritable_attribute :non_audited_columns, except

# handle includes (associations)
Expand All @@ -82,27 +86,27 @@ def acts_as_audited(options = {})
has_many :audits, :as => :auditable, :order => "#{Audit.quoted_table_name}.version"
attr_protected :audit_ids if options[:protect]
Audit.audited_class_names << self.to_s

after_create :audit_create_callback
before_update :audit_update_callback
after_destroy :audit_destroy_callback

attr_accessor :version

extend CollectiveIdea::Acts::Audited::SingletonMethods
include CollectiveIdea::Acts::Audited::InstanceMethods

write_inheritable_attribute :auditing_enabled, true
end
end

module InstanceMethods

# Temporarily turns off auditing while saving.
def save_without_auditing
without_auditing { save }
end

# Executes the block with the auditing callbacks disabled.
#
# @foo.without_auditing do
Expand All @@ -112,7 +116,7 @@ def save_without_auditing
def without_auditing(&block)
self.class.without_auditing(&block)
end

# Gets an array of the revisions available
#
# user.revisions.each do |revision|
Expand All @@ -126,32 +130,32 @@ def revisions(from_version = 1)
revision = self.audits.find_by_version(from_version).revision
Audit.reconstruct_attributes(audits) {|attrs| revision.revision_with(attrs) }
end

# Get a specific revision specified by the version number, or +:previous+
def revision(version)
revision_with Audit.reconstruct_attributes(audits_to(version))
end

def revision_at(date_or_time)
audits = self.audits.find(:all, :conditions => ["created_at <= ?", date_or_time])
revision_with Audit.reconstruct_attributes(audits) unless audits.empty?
end

def audited_attributes
attributes.except(*non_audited_columns)
end

def audit_associations
audit_associations
end

protected

def revision_with(attributes)
returning self.dup do |revision|
revision.send :instance_variable_set, '@attributes', self.attributes_before_type_cast
revision.attributes = attributes.reject {|attr,_| !respond_to?("#{attr}=") }
Audit.assign_revision_attributes(revision, attributes)

# Remove any association proxies so that they will be recreated
# and reference the correct object for this revision. The only way
# to determine if an instance variable is a proxy object is to
Expand All @@ -165,17 +169,16 @@ def revision_with(attributes)
end
end
end

private

def audited_changes(all = false)
attrs = all ? audited_attributes : changed_attributes
attrs.except(*non_audited_columns).inject([]) do |changes,(attr, old_value)|
changed_attributes.except(*non_audited_columns).inject({}) do |changes,(attr, old_value)|
changes << AuditChange.new(:field => attr, :old_value => old_value, :new_value => self[attr])
changes
end
end

def audits_to(version = nil)
if version == :previous
version = if self.version
Expand All @@ -188,7 +191,7 @@ def audits_to(version = nil)
end
audits.find(:all, :conditions => ['version <= ?', version])
end

def audit_create(user = nil)
write_audit(:action => 'create', :audit_changes => audited_changes(true), :user => user)
end
Expand All @@ -202,20 +205,20 @@ def audit_update(user = nil, changes = [])
def audit_destroy(user = nil)
write_audit(:action => 'destroy', :user => user, :audit_changes => audited_changes(true))
end

def write_audit(attrs)
self.audits.create attrs if auditing_enabled
end

CALLBACKS.each do |attr_name|
CALLBACKS.each do |attr_name|
alias_method "#{attr_name}_callback".to_sym, attr_name
end

def empty_callback #:nodoc:
end

end # InstanceMethods

module SingletonMethods
# Returns an array of columns that are audited. See non_audited_columns
def audited_columns
Expand All @@ -233,31 +236,38 @@ def without_auditing(&block)
disable_auditing
returning(block.call) { enable_auditing if auditing_was_enabled }
end

def disable_auditing
write_inheritable_attribute :auditing_enabled, false
end

def enable_auditing
write_inheritable_attribute :auditing_enabled, true
end

def disable_auditing_callbacks
class_eval do
CALLBACKS.each do |attr_name|
alias_method "#{attr_name}_callback", :empty_callback
end
end
end

def enable_auditing_callbacks
class_eval do
class_eval do
CALLBACKS.each do |attr_name|
alias_method "#{attr_name}_callback".to_sym, attr_name
end
end
end


# All audit operations during the block are recorded as being
# made by +user+. This is not model specific, the method is a
# convenience wrapper around #Audit.as_user.
def audit_as( user, &block )
Audit.as_user( user, &block )
end

end
end
end
Expand Down

0 comments on commit 88ef2b1

Please sign in to comment.