Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

add acts_as_audited plugin

  • Loading branch information...
commit 63e68693e493a6879a3f95e05ebcc0ff960a223e 1 parent 7f5a322
@timriley authored
Showing with 1,114 additions and 1 deletion.
  1. +23 −0 db/migrate/20080706055700_add_audits_table.rb
  2. +17 −1 db/schema.rb
  3. +2 −0  vendor/plugins/acts_as_audited/.gitignore
  4. +22 −0 vendor/plugins/acts_as_audited/CHANGELOG
  5. +67 −0 vendor/plugins/acts_as_audited/README
  6. +28 −0 vendor/plugins/acts_as_audited/Rakefile
  7. +7 −0 vendor/plugins/acts_as_audited/generators/audited_migration/USAGE
  8. +7 −0 vendor/plugins/acts_as_audited/generators/audited_migration/audited_migration_generator.rb
  9. +23 −0 vendor/plugins/acts_as_audited/generators/audited_migration/templates/migration.rb
  10. +9 −0 vendor/plugins/acts_as_audited/init.rb
  11. +224 −0 vendor/plugins/acts_as_audited/lib/acts_as_audited.rb
  12. +73 −0 vendor/plugins/acts_as_audited/lib/acts_as_audited/audit.rb
  13. +60 −0 vendor/plugins/acts_as_audited/lib/acts_as_audited/audit_sweeper.rb
  14. +142 −0 vendor/plugins/acts_as_audited/lib/acts_as_audited/dirty.rb
  15. +4 −0 vendor/plugins/acts_as_audited/tasks/acts_as_audited_tasks.rake
  16. +2 −0  vendor/plugins/acts_as_audited/test.txt
  17. +182 −0 vendor/plugins/acts_as_audited/test/acts_as_audited_test.rb
  18. +40 −0 vendor/plugins/acts_as_audited/test/audit_sweeper_test.rb
  19. +59 −0 vendor/plugins/acts_as_audited/test/audit_test.rb
  20. +21 −0 vendor/plugins/acts_as_audited/test/database.yml
  21. +3 −0  vendor/plugins/acts_as_audited/test/fixtures/user.rb
  22. +31 −0 vendor/plugins/acts_as_audited/test/schema.rb
  23. +68 −0 vendor/plugins/acts_as_audited/test/test_helper.rb
View
23 db/migrate/20080706055700_add_audits_table.rb
@@ -0,0 +1,23 @@
+class AddAuditsTable < ActiveRecord::Migration
+ def self.up
+ create_table :audits, :force => true do |t|
+ t.column :auditable_id, :integer
+ t.column :auditable_type, :string
+ t.column :user_id, :integer
+ t.column :user_type, :string
+ t.column :username, :string
+ t.column :action, :string
+ t.column :changes, :text
+ t.column :version, :integer, :default => 0
+ t.column :created_at, :datetime
+ end
+
+ add_index :audits, [:auditable_id, :auditable_type], :name => 'auditable_index'
+ add_index :audits, [:user_id, :user_type], :name => 'user_index'
+ add_index :audits, :created_at
+ end
+
+ def self.down
+ drop_table :audits
+ end
+end
View
18 db/schema.rb
@@ -9,7 +9,23 @@
#
# It's strongly recommended to check this file into your version control system.
-ActiveRecord::Schema.define(:version => 2) do
+ActiveRecord::Schema.define(:version => 20080706055700) do
+
+ create_table "audits", :force => true do |t|
+ t.integer "auditable_id", :limit => 11
+ t.string "auditable_type"
+ t.integer "user_id", :limit => 11
+ t.string "user_type"
+ t.string "username"
+ t.string "action"
+ t.text "changes"
+ t.integer "version", :limit => 11, :default => 0
+ t.datetime "created_at"
+ end
+
+ add_index "audits", ["auditable_id", "auditable_type"], :name => "auditable_index"
+ add_index "audits", ["user_id", "user_type"], :name => "user_index"
+ add_index "audits", ["created_at"], :name => "index_audits_on_created_at"
create_table "bj_config", :primary_key => "bj_config_id", :force => true do |t|
t.text "hostname"
View
2  vendor/plugins/acts_as_audited/.gitignore
@@ -0,0 +1,2 @@
+acts_as_audited_plugin.sqlite3.db
+test/debug.log
View
22 vendor/plugins/acts_as_audited/CHANGELOG
@@ -0,0 +1,22 @@
+ acts_as_audited ChangeLog
+-------------------------------------------------------------------------------
+* 2008-04-19 - refactored to make compatible with dirty tracking in edge rails
+ and to stop storing both old and new values in a single audit
+* 2008-04-18 - Fix NoMethodError when trying to access the :previous revision
+ on a model that doesn't have previous revisions [Alex Soto]
+* 2008-03-21 - added #changed_attributes to get access to the changes before a
+ save [Chris Parker]
+* 2007-12-16 - Added #revision_at for retrieving a revision from a specific
+ time [Jacob Atzen]
+* 2007-12-16 - Fix error when getting revision from audit with no changes
+ [Geoffrey Wiseman]
+* 2007-12-16 - Remove dependency on acts_as_list
+* 2007-06-17 - Added support getting previous revisions
+* 2006-11-17 - Replaced use of singleton User.current_user with cache sweeper
+ implementation for auditing the user that made the change
+* 2006-11-17 - added migration generator
+* 2006-08-14 - incorporated changes from Michael Schuerig to write_attribute
+ that saves the new value after every change and not just the
+ first, and performs proper type-casting before doing comparisons
+* 2006-08-14 - The "changes" are now saved as a serialized hash
+* 2006-07-21 - initial version
View
67 vendor/plugins/acts_as_audited/README
@@ -0,0 +1,67 @@
+= acts_as_audited
+
+acts_as_audited is an ActiveRecord extension that logs all changes to your models in an audits table.
+
+== Installation
+
+* Install the plugin into your rails app
+ If you are using Rails 2.1:
+
+ script/plugin install git://github.com/collectiveidea/acts_as_audited.git
+
+ For versions prior to 2.1:
+
+ git clone git://github.com/collectiveidea/acts_as_audited.git vendor/plugins/acts_as_audited
+
+* Generate the migration
+ script/generate audited_migration add_audits_table
+ rake db:migrate
+
+== Auditing in Rails
+
+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.
+
+ class ApplicationController < ActionController::Base
+ audit User, List, Item
+ protected
+ def current_user
+ @user ||= User.find(session[:user])
+ end
+ end
+
+== Customizing
+
+To get auditing outside of Rails, or to customize which fields are audited within Rails, you can explicitly declare <tt>acts_as_audited</tt> on your models:
+
+ class User < ActiveRecord::Base
+ acts_as_audited :except => [:password, :mistress]
+ end
+
+See http://opensoul.org/2006/07/21/acts_as_audited for more information.
+
+== Caveats
+
+Auditing with user support depends on Rails' caching mechanisms, therefore auditing isn't enabled during development mode. To test that auditing is working, start up your app in production mode, or change the following options in config/environments/environment.rb:
+
+ config.cache_classes = true
+ config.action_controller.perform_caching = true
+
+=== ActiveScaffold
+
+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:
+
+ class ApplicationController < ActionController::Base
+ audit MyModel, :only => [:create, :update, :destroy]
+ end
+
+== Upgrading
+
+To upgrade from an older version, add a migration with:
+
+ # to version 0.3
+ add_column :audits, :user_type, :string
+ add_column :audits, :username, :string
+
+ # to version 0.4
+ add_column :audits, :version, :integer, :default => 0
+
View
28 vendor/plugins/acts_as_audited/Rakefile
@@ -0,0 +1,28 @@
+require 'rake'
+require 'rake/testtask'
+require 'rake/rdoctask'
+
+desc 'Default: run unit tests.'
+task :default => :test
+
+desc 'Test the acts_as_audited plugin'
+Rake::TestTask.new(:test) do |t|
+ t.libs << 'lib'
+ t.pattern = 'test/**/*_test.rb'
+ t.verbose = true
+end
+
+desc 'Generate documentation for the acts_as_audited plugin.'
+Rake::RDocTask.new(:rdoc) do |rdoc|
+ rdoc.rdoc_dir = 'doc'
+ rdoc.title = 'acts_as_audited'
+ rdoc.options << '--line-numbers' << '--inline-source'
+ rdoc.rdoc_files.include('README')
+ rdoc.rdoc_files.include('lib/**/*.rb')
+end
+
+desc "Publish the rdocs"
+task :publish => [:rdoc] do
+ `ssh host.collectiveidea.com "mkdir -p /var/www/vhosts/source.collectiveidea.com/public/dist/api/acts_as_audited"`
+ Rake::SshDirPublisher.new("host.collectiveidea.com", "/var/www/vhosts/source.collectiveidea.com/public/dist/api/acts_as_audited", "doc").upload
+end
View
7 vendor/plugins/acts_as_audited/generators/audited_migration/USAGE
@@ -0,0 +1,7 @@
+Description:
+ The audited migration generator creates a migration to add the audits table.
+
+Example:
+ ./script/generate audited_migration add_audits_table
+
+ This will create a migration in db/migrate/. Run "rake db:migrate" to update your database.
View
7 vendor/plugins/acts_as_audited/generators/audited_migration/audited_migration_generator.rb
@@ -0,0 +1,7 @@
+class AuditedMigrationGenerator < Rails::Generator::NamedBase
+ def manifest
+ record do |m|
+ m.migration_template 'migration.rb', 'db/migrate'
+ end
+ end
+end
View
23 vendor/plugins/acts_as_audited/generators/audited_migration/templates/migration.rb
@@ -0,0 +1,23 @@
+class <%= class_name %> < ActiveRecord::Migration
+ def self.up
+ create_table :audits, :force => true do |t|
+ t.column :auditable_id, :integer
+ t.column :auditable_type, :string
+ t.column :user_id, :integer
+ t.column :user_type, :string
+ t.column :username, :string
+ t.column :action, :string
+ t.column :changes, :text
+ t.column :version, :integer, :default => 0
+ t.column :created_at, :datetime
+ end
+
+ add_index :audits, [:auditable_id, :auditable_type], :name => 'auditable_index'
+ add_index :audits, [:user_id, :user_type], :name => 'user_index'
+ add_index :audits, :created_at
+ end
+
+ def self.down
+ drop_table :audits
+ end
+end
View
9 vendor/plugins/acts_as_audited/init.rb
@@ -0,0 +1,9 @@
+require 'acts_as_audited/audit'
+require 'acts_as_audited'
+
+ActiveRecord::Base.send :include, CollectiveIdea::Acts::Audited
+
+if defined?(ActionController) and defined?(ActionController::Base)
+ require 'acts_as_audited/audit_sweeper'
+ ActionController::Base.send :include, CollectiveIdea::ActionController::Audited
+end
View
224 vendor/plugins/acts_as_audited/lib/acts_as_audited.rb
@@ -0,0 +1,224 @@
+# 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
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+module CollectiveIdea #:nodoc:
+ module Acts #:nodoc:
+ # Specify this act if you want changes to your model to be saved in an
+ # audit table. This assumes there is an audits table ready.
+ #
+ # class User < ActiveRecord::Base
+ # acts_as_audited
+ # end
+ #
+ # See <tt>CollectiveIdea::Acts::Audited::ClassMethods#acts_as_audited</tt>
+ # for configuration options
+ module Audited #:nodoc:
+ CALLBACKS = [:audit_create, :audit_update, :audit_destroy]
+
+ def self.included(base) # :nodoc:
+ base.extend ClassMethods
+ end
+
+ module ClassMethods
+ # == Configuration options
+ #
+ # * <tt>except</tt> - 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']
+ #
+ # You can add to those by passing one or an array of fields to skip.
+ #
+ # class User < ActiveRecord::Base
+ # acts_as_audited :except => :password
+ # end
+ #
+ def acts_as_audited(options = {})
+ # don't allow multiple calls
+ return if self.included_modules.include?(CollectiveIdea::Acts::Audited::InstanceMethods)
+
+ class_inheritable_reader :non_audited_columns
+ class_inheritable_reader :auditing_enabled
+
+ except = [self.primary_key, inheritance_column, 'lock_version', 'created_at', 'updated_at']
+ except |= [options[:except]].flatten.collect(&:to_s) if options[:except]
+ write_inheritable_attribute :non_audited_columns, except
+
+ has_many :audits, :as => :auditable, :order => 'audits.version desc'
+ attr_protected :audit_ids
+ Audit.audited_classes << self
+
+ 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
+ unless ActiveRecord.const_defined? 'Dirty'
+ require 'acts_as_audited/dirty'
+ include CollectiveIdea::Acts::Audited::Dirty
+ end
+
+ write_inheritable_attribute :auditing_enabled, true
+ end
+ end
+
+ module InstanceMethods
+
+ def changed_audited_attributes
+ attributes.slice(*changed_attributes.keys).except(*non_audited_columns)
+ end
+
+ # Returns the attributes that are audited
+ def audited_attributes
+ attributes.except(*non_audited_columns)
+ end
+
+ # 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
+ # @foo.save
+ # end
+ #
+ def without_auditing(&block)
+ self.class.without_auditing(&block)
+ end
+
+ # Gets an array of the revisions available
+ #
+ # user.revisions.each do |revision|
+ # user.name
+ # user.version
+ # end
+ #
+ def revisions(from_version = 1)
+ changes_from(from_version) {|attributes| revision_with(attributes) }
+ end
+
+ # Get a specific revision
+ def revision(version)
+ revision_with changes_from(version)
+ end
+
+ def revision_at(date_or_time)
+ audit = audits.find(:first, :conditions => ["created_at <= ?", date_or_time],
+ :order => "created_at DESC")
+ revision_with changes_from(audit.version) if audit
+ end
+
+ private
+
+ def changes_from(version = 1, &block)
+ if version == :previous
+ last_audit = audits.find(:first)
+ version = last_audit ? last_audit.version : 1
+ end
+ revisions = audits.find(:all, :conditions => ['version >= ?', version])
+ Audit.reconstruct_attributes(revisions, &block)
+ end
+
+ def revision_with(attributes)
+ returning self.dup do |revision|
+ revision.send :instance_variable_set, '@attributes', self.attributes_before_type_cast
+ revision.attributes = attributes
+ end
+ end
+
+ def audit_create(user = nil)
+ write_audit(:action => :create, :changes => audited_attributes, :user => user)
+ end
+
+ def audit_update(user = nil)
+ unless (changes = changed_audited_attributes).empty?
+ write_audit(:action => :update, :changes => changes, :user => user)
+ end
+ end
+
+ def audit_destroy(user = nil)
+ write_audit(:action => :destroy, :user => user)
+ end
+
+ def write_audit(attrs)
+ self.audits.create attrs if auditing_enabled
+ end
+
+ 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
+ self.columns.select { |c| !non_audited_columns.include?(c.name) }
+ end
+
+ # Executes the block with auditing disabled.
+ #
+ # Foo.without_auditing do
+ # @foo.save
+ # end
+ #
+ def without_auditing(&block)
+ auditing_was_enabled = auditing_enabled
+ 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
+ CALLBACKS.each do |attr_name|
+ alias_method "#{attr_name}_callback".to_sym, attr_name
+ end
+ end
+ end
+
+ end
+ end
+ end
+end
View
73 vendor/plugins/acts_as_audited/lib/acts_as_audited/audit.rb
@@ -0,0 +1,73 @@
+require 'set'
+
+# Audit saves the changes to ActiveRecord models. It has the following attributes:
+#
+# * <tt>auditable</tt>: the ActiveRecord model that was changed
+# * <tt>user</tt>: the user that performed the change; a string or an ActiveRecord model
+# * <tt>action</tt>: one of create, update, or delete
+# * <tt>changes</tt>: a serialized hash of all the changes
+# * <tt>created_at</tt>: Time that the change was performed
+#
+class Audit < ActiveRecord::Base
+ belongs_to :auditable, :polymorphic => true
+ belongs_to :user, :polymorphic => true
+
+ before_create :set_version_number
+
+ serialize :changes
+
+ cattr_accessor :audited_classes
+ self.audited_classes = Set.new
+
+ # Allows user to be set to either a string or an ActiveRecord object
+ def user_as_string=(user) #:nodoc:
+ # reset both either way
+ self.user_as_model = self.username = nil
+ user.is_a?(ActiveRecord::Base) ?
+ self.user_as_model = user :
+ self.username = user
+ end
+ alias_method :user_as_model=, :user=
+ alias_method :user=, :user_as_string=
+
+ def user_as_string #:nodoc:
+ self.user_as_model || self.username
+ end
+ alias_method :user_as_model, :user
+ alias_method :user, :user_as_string
+
+ def revision
+ attributes = self.class.reconstruct_attributes(ancestors).merge({:version => version})
+ clazz = auditable_type.constantize
+ returning clazz.find_by_id(auditable_id) || clazz.new do |m|
+ m.attributes = attributes
+ end
+ end
+
+ def ancestors
+ self.class.find(:all, :order => 'version',
+ :conditions => ['auditable_id = ? and auditable_type = ? and version <= ?',
+ auditable_id, auditable_type, version])
+ end
+
+ def self.reconstruct_attributes(audits)
+ changes = {}
+ result = audits.collect do |audit|
+ changes.merge!((audit.changes || {}).merge!(:version => audit.version))
+ yield changes if block_given?
+ end
+ block_given? ? result : changes
+ end
+
+private
+
+ def set_version_number
+ max = self.class.maximum(:version,
+ :conditions => {
+ :auditable_id => auditable_id,
+ :auditable_type => auditable_type
+ }) || 0
+ self.version = max + 1
+ end
+
+end
View
60 vendor/plugins/acts_as_audited/lib/acts_as_audited/audit_sweeper.rb
@@ -0,0 +1,60 @@
+
+module CollectiveIdea #:nodoc:
+ module ActionController #:nodoc:
+ module Audited #:nodoc:
+
+ def self.included(base) # :nodoc:
+ base.extend ClassMethods
+ end
+
+ module ClassMethods
+ # Declare models that should be audited in your controller
+ #
+ # class ApplicationController < ActionController::Base
+ # audit User, Widget
+ # end
+ #
+ # You can also specify an options hash which will be passed on to
+ # Rails' cache_sweeper call:
+ #
+ # audit User, :only => [:create, :edit, :destroy]
+ #
+ def audit(*models)
+ options = models.last.is_a?(Hash) ? models.pop : {}
+ models.each do |clazz|
+ clazz.send :acts_as_audited
+ # disable ActiveRecord callbacks, which are replaced by the AuditSweeper
+ clazz.send :disable_auditing_callbacks
+ end
+ AuditSweeper.class_eval do
+ observe *models
+ end
+ class_eval do
+ cache_sweeper :audit_sweeper, options
+ end
+ end
+ end
+
+ end
+ end
+end
+
+class AuditSweeper < ActionController::Caching::Sweeper #:nodoc:
+
+ def after_create(record)
+ record.send(:audit_create, current_user)
+ end
+
+ def after_destroy(record)
+ record.send(:audit_destroy, current_user)
+ end
+
+ def before_update(record)
+ record.send(:audit_update, current_user)
+ end
+
+ def current_user
+ controller.send :current_user if controller.respond_to?(:current_user)
+ end
+
+end
View
142 vendor/plugins/acts_as_audited/lib/acts_as_audited/dirty.rb
@@ -0,0 +1,142 @@
+module CollectiveIdea #:nodoc
+ module Acts #:nodoc
+ module Audited
+ # Dirty tracking module taken from edge rails:
+ # http://dev.rubyonrails.org/changeset/9127
+ #
+ # Track unsaved attribute changes.
+ #
+ # A newly instantiated object is unchanged:
+ # person = Person.find_by_name('uncle bob')
+ # person.changed? # => false
+ #
+ # Change the name:
+ # person.name = 'Bob'
+ # person.changed? # => true
+ # person.name_changed? # => true
+ # person.name_was # => 'uncle bob'
+ # person.name_change # => ['uncle bob', 'Bob']
+ # person.name = 'Bill'
+ # person.name_change # => ['uncle bob', 'Bill']
+ #
+ # Save the changes:
+ # person.save
+ # person.changed? # => false
+ # person.name_changed? # => false
+ #
+ # Assigning the same value leaves the attribute unchanged:
+ # person.name = 'Bill'
+ # person.name_changed? # => false
+ # person.name_change # => nil
+ #
+ # Which attributes have changed?
+ # person.name = 'bob'
+ # person.changed # => ['name']
+ # person.changes # => { 'name' => ['Bill', 'bob'] }
+ #
+ # Before modifying an attribute in-place:
+ # person.name_will_change!
+ # person.name << 'by'
+ # person.name_change # => ['uncle bob', 'uncle bobby']
+ module Dirty
+ def self.included(base)
+ base.attribute_method_suffix '_changed?', '_change', '_will_change!', '_was'
+ base.alias_method_chain :write_attribute, :dirty
+ base.alias_method_chain :save, :dirty
+ base.alias_method_chain :save!, :dirty
+ # base.alias_method_chain :update, :dirty
+ end
+
+ # Do any attributes have unsaved changes?
+ # person.changed? # => false
+ # person.name = 'bob'
+ # person.changed? # => true
+ def changed?
+ !changed_attributes.empty?
+ end
+
+ # List of attributes with unsaved changes.
+ # person.changed # => []
+ # person.name = 'bob'
+ # person.changed # => ['name']
+ def changed
+ changed_attributes.keys
+ end
+
+ # Map of changed attrs => [original value, new value]
+ # person.changes # => {}
+ # person.name = 'bob'
+ # person.changes # => { 'name' => ['bill', 'bob'] }
+ def changes
+ changed.inject({}) { |h, attr| h[attr] = attribute_change(attr); h }
+ end
+
+
+ # Clear changed attributes after they are saved.
+ def save_with_dirty(*args) #:nodoc:
+ save_without_dirty(*args)
+ ensure
+ changed_attributes.clear
+ end
+
+ # Clear changed attributes after they are saved.
+ def save_with_dirty!(*args) #:nodoc:
+ save_without_dirty!(*args)
+ ensure
+ changed_attributes.clear
+ end
+
+ private
+ # Map of change attr => original value.
+ def changed_attributes
+ @changed_attributes ||= {}
+ end
+
+ # Handle *_changed? for method_missing.
+ def attribute_changed?(attr)
+ changed_attributes.include?(attr)
+ end
+
+ # Handle *_change for method_missing.
+ def attribute_change(attr)
+ [changed_attributes[attr], __send__(attr)] if attribute_changed?(attr)
+ end
+
+ # Handle *_was for method_missing.
+ def attribute_was(attr)
+ attribute_changed?(attr) ? changed_attributes[attr] : __send__(attr)
+ end
+
+ # Handle *_will_change! for method_missing.
+ def attribute_will_change!(attr)
+ changed_attributes[attr] = clone_attribute_value(:read_attribute, attr)
+ end
+
+ # Wrap write_attribute to remember original attribute value.
+ def write_attribute_with_dirty(attr, value)
+ attr = attr.to_s
+
+ # The attribute already has an unsaved change.
+ unless changed_attributes.include?(attr)
+ old = clone_attribute_value(:read_attribute, attr)
+
+ # Remember the original value if it's different.
+ typecasted = if column = column_for_attribute(attr)
+ column.type_cast(value)
+ else
+ value
+ end
+ changed_attributes[attr] = old unless old == typecasted
+ end
+
+ # Carry on.
+ write_attribute_without_dirty(attr, value)
+ end
+
+ # def update_with_dirty
+ # update_without_dirty(changed)
+ # end
+ end
+ end
+ end
+end
View
4 vendor/plugins/acts_as_audited/tasks/acts_as_audited_tasks.rake
@@ -0,0 +1,4 @@
+# desc "Explaining what the task does"
+# task :acts_as_audited do
+# # Task goes here
+# end
View
2  vendor/plugins/acts_as_audited/test.txt
@@ -0,0 +1,2 @@
+again
+grr
View
182 vendor/plugins/acts_as_audited/test/acts_as_audited_test.rb
@@ -0,0 +1,182 @@
+require File.expand_path(File.dirname(__FILE__) + '/test_helper')
+
+class ActsAsAuditedTest < Test::Unit::TestCase
+
+ def test_acts_as_authenticated_declaration_includes_instance_methods
+ assert_kind_of CollectiveIdea::Acts::Audited::InstanceMethods, User.new
+ end
+
+ def test_acts_as_authenticated_declaration_extends_singleton_methods
+ assert_kind_of CollectiveIdea::Acts::Audited::SingletonMethods, User
+ end
+
+ def test_audited_attributes
+ attrs = {'name' => nil, 'username' => nil, 'logins' => 0, 'activated' => nil}
+ assert_equal attrs, User.new.audited_attributes
+ end
+
+ def test_non_audited_columns
+ ['created_at', 'updated_at', 'lock_version', 'id', 'password'].each do |column|
+ assert User.non_audited_columns.include?(column), "non_audited_columns should include #{column}."
+ end
+ end
+
+ def test_doesnt_save_non_audited_columns
+ u = create_user
+ assert !u.audits.first.changes.include?('created_at'), 'created_at should not be audited'
+ assert !u.audits.first.changes.include?('updated_at'), 'updated_at should not be audited'
+ assert !u.audits.first.changes.include?('password'), 'password should not be audited'
+ end
+
+ def test_save_audit
+ u = nil
+ assert_difference(Audit, :count) { u = create_user }
+ assert_difference(Audit, :count) { assert u.update_attribute(:name, "Someone") }
+ assert_no_difference(Audit, :count) { assert u.save }
+ assert_difference(Audit, :count) { assert u.destroy }
+ end
+
+ def test_save_without_auditing
+ assert_no_difference Audit, :count do
+ u = User.new(:name => 'Brandon')
+ assert u.save_without_auditing
+ end
+ end
+
+ def test_without_auditing_block
+ assert_no_difference Audit, :count do
+ User.without_auditing { User.create(:name => 'Brandon') }
+ end
+ end
+
+ def test_changed?
+ u = create_user
+ assert !u.changed?
+ u.name = "Bobby"
+ assert u.changed?
+ assert u.name_changed?
+ assert !u.username_changed?
+ end
+
+ def test_clears_changed_attributes_after_save
+ u = User.new(:name => 'Brandon')
+ assert u.changed?
+ u.save
+ assert !u.changed?
+ end
+
+ def test_type_casting
+ u = create_user(:logins => 0, :activated => true)
+ assert_no_difference(Audit, :count) { u.update_attribute :logins, '0' }
+ assert_no_difference(Audit, :count) { u.update_attribute :logins, 0 }
+ assert_no_difference(Audit, :count) { u.update_attribute :activated, true }
+ assert_no_difference(Audit, :count) { u.update_attribute :activated, 1 }
+ end
+
+ def test_that_changes_is_a_hash
+ audit = create_user.audits.first
+ audit.reload
+ assert audit.changes.is_a?(Hash)
+ end
+
+ def test_save_without_modifications
+ u = create_user
+ u.reload
+ assert_nothing_raised do
+ assert !u.changed?
+ u.save!
+ end
+ end
+
+ def test_revisions_should_return_array
+ u = create_versions
+ assert_kind_of Array, u.revisions
+ u.revisions.each {|version| assert_kind_of User, version }
+ end
+
+ def test_latest_revision_first
+ u = User.create(:name => 'Brandon')
+ assert_equal 1, u.revisions.size
+ assert_equal 'Brandon', u.revisions[0].name
+
+ u.update_attribute :name, 'Foobar'
+ assert_equal 2, u.revisions.size
+ assert_equal 'Foobar', u.revisions[0].name
+ assert_equal 'Brandon', u.revisions[1].name
+ end
+
+ def test_revisions_without_changes
+ u = User.create
+ assert_nothing_raised do
+ assert_equal 1, u.revisions.size
+ end
+ end
+
+ # FIXME: figure out a better way to test this
+ def test_revision_at
+ u = create_user
+ Audit.update(u.audits.first.id, :created_at => 1.hour.ago)
+ u.update_attributes :name => 'updated'
+ assert_equal 1, u.revision_at(2.minutes.ago).version
+ end
+
+ def test_revision_at_before_record_created
+ u = create_user
+ assert_nil u.revision_at(1.week.ago)
+ end
+
+ def test_get_specific_revision
+ u = create_versions(5)
+ revision = u.revision(3)
+ assert_kind_of User, revision
+ assert_equal 3, revision.version
+ assert_equal 'Foobar 3', revision.name
+ end
+
+ def test_get_previous_revision
+ u = create_versions(5)
+ revision = u.revision(:previous)
+ assert_equal 5, revision.version
+ assert_equal u.revision(5), revision
+ end
+
+ def test_revision_marks_attributes_changed
+ u = create_versions(2)
+ assert u.revision(1).name_changed?
+ end
+
+ def test_save_revision_records_audit
+ u = create_versions(2)
+ assert_difference Audit, :count do
+ assert u.revision(1).save
+ end
+ end
+
+ def test_without_previous_audits
+ user = create_user
+ user.audits.destroy_all
+ assert_nothing_raised(NoMethodError) { user.revision(:previous) }
+ end
+
+ def test_without_auditing
+ u = create_user
+ assert_no_difference Audit, :count do
+ User.without_auditing do
+ u.update_attribute :name, 'Changed'
+ end
+ end
+ assert_difference Audit, :count do
+ u.update_attribute :name, 'Changed Again'
+ end
+ end
+
+ def test_disable_auditing_callbacks
+ User.disable_auditing_callbacks
+ assert_no_difference Audit, :count do
+ create_user
+ end
+ ensure
+ User.enable_auditing_callbacks
+ end
+
+end
View
40 vendor/plugins/acts_as_audited/test/audit_sweeper_test.rb
@@ -0,0 +1,40 @@
+require File.expand_path(File.dirname(__FILE__) + '/test_helper')
+
+class Company < ActiveRecord::Base
+end
+class AuditsController < ActionController::Base
+ audit Company
+ attr_accessor :current_user
+
+ def audit
+ @company = Company.create
+ render :nothing => true
+ end
+
+end
+AuditsController.view_paths = [File.expand_path(File.dirname(__FILE__) + "/../fixtures")]
+ActionController::Routing::Routes.draw {|m| m.connect ':controller/:action/:id' }
+
+class AuditSweeperTest < Test::Unit::TestCase
+
+ def setup
+ @controller = AuditsController.new
+ @controller.logger = Logger.new(nil)
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ @request.host = "www.example.com"
+ end
+
+ def test_calls_acts_as_audited_on_non_audited_models
+ assert_kind_of CollectiveIdea::Acts::Audited::SingletonMethods, Company
+ end
+
+ def test_audits_user
+ user = @controller.current_user = create_user
+ assert_difference Audit, :count do
+ post :audit
+ end
+ assert_equal user, assigns(:company).audits.last.user
+ end
+
+end
View
59 vendor/plugins/acts_as_audited/test/audit_test.rb
@@ -0,0 +1,59 @@
+require File.expand_path(File.dirname(__FILE__) + '/test_helper')
+
+class AuditedTest < Test::Unit::TestCase
+
+ def test_set_user_to_model
+ @user = User.new :name => "testing"
+ @audit = Audit.new :user => @user
+ assert_equal @user, @audit.user
+ end
+
+ def test_set_user_to_nil
+ test_set_user_to_model
+ @audit.user = nil
+ assert_nil @audit.user
+ assert_nil @audit[:user_id]
+ assert_nil @audit[:user_type]
+ assert_nil @audit[:username]
+ end
+
+ def test_set_user_to_string
+ @audit = Audit.new :user => "testing"
+ assert_equal "testing", @audit.user
+ end
+
+ def test_set_to_string_then_model
+ @user = User.new :name => "testing"
+ @audit = Audit.new :user => "testing"
+ @audit.user = @user
+ assert_equal @user, @audit.user
+ assert_nil @audit.username
+ end
+
+ def test_revision
+ user = User.create :name => "1"
+ 5.times {|i| user.update_attribute :name, (i + 2).to_s }
+ user.audits.each do |audit|
+ assert_equal audit.version.to_s, audit.revision.name
+ end
+ end
+
+ def test_revision_for_deleted_model
+ user = User.create :name => "1"
+ user.destroy
+ revision = user.audits.last.revision
+ assert_equal user.name, revision.name
+ assert revision.new_record?
+ end
+
+ def test_sets_version_number_on_create
+ user = User.create! :name => "Set Version Number"
+ assert_equal 1, user.audits.last.version
+ user.update_attribute :name, "Set to 2"
+ assert_equal 2, user.audits(true).first.version
+ assert_equal 1, user.audits(true).last.version
+ user.destroy
+ assert_equal 3, user.audits(true).first.version
+ end
+
+end
View
21 vendor/plugins/acts_as_audited/test/database.yml
@@ -0,0 +1,21 @@
+sqlite:
+ :adapter: sqlite
+ :dbfile: acts_as_audited_plugin.sqlite.db
+sqlite3mem:
+ :adapter: sqlite3
+ :dbfile: ":memory:"
+sqlite3:
+ :adapter: sqlite3
+ :dbfile: acts_as_audited_plugin.sqlite3.db
+postgresql:
+ :adapter: postgresql
+ :username: postgres
+ :password: postgres
+ :database: acts_as_audited_plugin_test
+ :min_messages: ERROR
+mysql:
+ :adapter: mysql
+ :host: localhost
+ :username: rails
+ :password:
+ :database: acts_as_audited_plugin_test
View
3  vendor/plugins/acts_as_audited/test/fixtures/user.rb
@@ -0,0 +1,3 @@
+class User < ActiveRecord::Base
+ acts_as_audited :except => :password
+end
View
31 vendor/plugins/acts_as_audited/test/schema.rb
@@ -0,0 +1,31 @@
+ActiveRecord::Schema.define(:version => 0) do
+ create_table :users, :force => true do |t|
+ t.column :name, :string
+ t.column :username, :string
+ t.column :password, :string
+ t.column :activated, :boolean
+ t.column :logins, :integer, :default => 0
+ t.column :created_at, :datetime
+ t.column :updated_at, :datetime
+ end
+
+ create_table :companies, :force => true do |t|
+ t.column :name, :string
+ end
+
+ create_table :audits, :force => true do |t|
+ t.column :auditable_id, :integer
+ t.column :auditable_type, :string
+ t.column :user_id, :integer
+ t.column :user_type, :string
+ t.column :username, :string
+ t.column :action, :string
+ t.column :changes, :text
+ t.column :version, :integer, :default => 0
+ t.column :created_at, :datetime
+ end
+
+ add_index :audits, [:auditable_id, :auditable_type], :name => 'auditable_index'
+ add_index :audits, [:user_id, :user_type], :name => 'user_index'
+ add_index :audits, :created_at
+end
View
68 vendor/plugins/acts_as_audited/test/test_helper.rb
@@ -0,0 +1,68 @@
+$:.unshift(File.dirname(__FILE__) + '/../lib')
+
+require 'test/unit'
+require 'rubygems'
+require 'active_record'
+require 'action_controller'
+require 'action_view'
+require File.dirname(__FILE__) + '/../init.rb'
+
+require 'active_record/fixtures'
+require 'action_controller/test_process'
+
+config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
+ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log")
+ActiveRecord::Base.establish_connection(config[ENV['DB'] || 'sqlite3mem'])
+
+ActiveRecord::Migration.verbose = false
+load(File.dirname(__FILE__) + "/schema.rb")
+
+Test::Unit::TestCase.fixture_path = File.dirname(__FILE__) + "/fixtures/"
+$LOAD_PATH.unshift(Test::Unit::TestCase.fixture_path)
+
+# load model
+require 'user'
+
+class Test::Unit::TestCase #:nodoc:
+ def create_fixtures(*table_names)
+ if block_given?
+ Fixtures.create_fixtures(Test::Unit::TestCase.fixture_path, table_names) { yield }
+ else
+ Fixtures.create_fixtures(Test::Unit::TestCase.fixture_path, table_names)
+ end
+ end
+
+ # Turn off transactional fixtures if you're working with MyISAM tables in MySQL
+ self.use_transactional_fixtures = true
+
+ # Instantiated fixtures are slow, but give you @david where you otherwise would need people(:david)
+ self.use_instantiated_fixtures = false
+
+ # Add more helper methods to be used by all tests here...
+
+ # http://project.ioni.st/post/217#post-217
+ def assert_difference(object, method = nil, difference = 1)
+ initial_value = object.send(method)
+ yield
+ assert_equal initial_value + difference, object.send(method), "#{object}##{method}"
+ end
+
+ def assert_no_difference(object, method, &block)
+ assert_difference object, method, 0, &block
+ end
+
+ def create_user(attrs = {})
+ User.create({:name => 'Brandon', :username => 'brandon', :password => 'password'}.merge(attrs))
+ end
+
+ def create_versions(n = 2)
+ returning User.create(:name => 'Foobar 1') do |u|
+ (n - 1).times do |i|
+ u.update_attribute :name, "Foobar #{i + 2}"
+ end
+ u.reload
+ end
+
+ end
+
+end
Please sign in to comment.
Something went wrong with that request. Please try again.