a) I installed the acts_as_audited plugin (which works in Prorduction
Mode only) and activated it with only 1 line of code at
application.rb
audit User,Customer,Receivables
b) In my customer Controller I have
config.nested.add_link("Receivables", [:receivables])
c) Works perfectly (ie changes captured in the Audits table) until I
tried to access one of the nested tables (ie Receivables) on AS and
I
consistently got this error 500 message
NoMethodError (undefined method `controller_name' for nil:NilClass):
C:/InstantRails/ruby/lib/ruby/gems/1.8/gems/actionpack-1.13.3/
lib/
action_controller/caching.rb:602:in `callback'
C:/InstantRails/ruby/lib/ruby/gems/1.8/gems/actionpack-1.13.3/
lib/
action_controller/caching.rb:595:in `after'
Any workaround would be apreciated
TQ
Tested with trunk and still have same problem
ie
c) Works perfectly (ie changes captured in the Audits table) until I
tried to access one of the nested tables (ie Receivables) on AS and
consistently got this error 500 message
NoMethodError (undefined method `controller_name' for nil:NilClass):
C:/InstantRails/ruby/lib/ruby/gems/1.8/gems/actionpack-1.13.3/
lib/
I think acts_as_audited is so simple to use and it is a pity I may
have to abandon it as AS nesting does not appear to support it <sigh>
Any help is really appreciated. Lance/Richard/Tim/Ed, any chance you
can help ?
-Doug
Thanx for responding !
See my in-line comments marked with 'cch'
>On Aug 25, 11:28 pm, "Lance Ivy" <la...@cainlevy.net> wrote:
> acts_as_audited is great ... i wouldn't abandon it just because
> activescaffold has trouble displaying the association! i'm not clear how
> acts_as_audited is creating the problem here. this only happens when you're
> on the customer controller, displaying nested receivables?
cch:
So long as I add this line in application.rb (which is how
acts_as_audited is activated, so simple compared with
acts_as_versioned :-) )
audit User,Customer,Receivables
the association won't work in user, Customer nor Receivables ie I get
the 500 error
Once I comment out the said line, everything thing is OK except that
auditing is now de-activated
As I understand it, acts_as_audited uses audit_sweeper and only works
in Production Mode
>where does the
> audits table come into the mix?
cch: It does not. The Big Decimal issue reported by me earlier due to
serialiasation of data in audits->changes have already been solved
and is a unconnected issue.
>
BTW, for your convenience, I am pasting the 3 rbs that is in the \lib
director whcih may be of some help to tweak AS
audit_sweeper.rb
=============
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
#
def audit(*models)
models.each do |clazz|
clazz.send :acts_as_audited unless clazz.respond_to?
(:disable_auditing)
# disable ActiveRecord callbacks, which are replaced by
the AuditSweeper
clazz.send :disable_auditing
end
AuditSweeper.class_eval do
observe *models
end
class_eval do
cache_sweeper :audit_sweeper
end
end
end
end
end
end
class AuditSweeper < ActionController::Caching::Sweeper #:nodoc:
def after_create(record)
record.send(:write_audit, :create, current_user)
end
def after_destroy(record)
record.send(:write_audit, :destroy, current_user)
end
def after_update(record)
record.send(:write_audit, :update, current_user)
end
def current_user
controller.send :current_user if controller.respond_to?
(:current_user)
end
end
audit.rb
======
#
# 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
acts_as_list :column => :version, :scope => 'auditable_id =
#{auditable_id} AND auditable_type = \'#{auditable_type}\''
serialize :changes
cattr_accessor :audited_classes
self.audited_classes = []
# 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 = current_user.login # 24/8/07user
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|
attributes = (audit.changes || {}).inject({}) do |attrs, (name,
(_,value))|
attrs[name] = value
attrs
end
changes.merge!(attributes.merge!(:version => audit.version))
yield changes if block_given?
end
block_given? ? result : changes
end
end
acts_as_audite.rb
# 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 =
[:clear_changed_attributes, :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)
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
class_eval do
extend CollectiveIdea::Acts::Audited::SingletonMethods
has_many :audits, :as => :auditable, :order =>
'audits.version desc'
attr_protected :audit_ids
Audit.audited_classes << self unless
Audit.audited_classes.include?(self)
after_create :audit_create
after_update :audit_update
after_destroy :audit_destroy
after_save :clear_changed_attributes
attr_accessor :version
alias_method_chain :write_attribute, :auditing
write_inheritable_attribute :auditing_enabled, true
end
end
end
module InstanceMethods
# Temporarily turns off auditing while saving.
def save_without_auditing
without_auditing { save }
end
# Returns an array of attribute keys that are audited. See
non_audited_columns
def audited_attributes
self.attributes.keys.select { |k| !
self.non_audited_columns.include?(k) }
end
# If called with no parameters, gets whether the current model
has changed.
# If called with a single parameter, gets whether the
parameter has changed.
def changed?(attr_name = nil)
@changed_attributes ||= {}
attr_name ? @changed_attributes.include?(attr_name.to_s) : !
@changed_attributes.empty?
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_version) {|attributes|
revision_with(attributes) }
end
# Get a specific revision
def revision(version)
revision_with changes(version)
end
private
def changes(from_version = 1)
from_version = audits.find(:first).version if from_version
== :previous
changes = {}
result = audits.find(:all, :conditions => ['version >= ?',
from_version]).collect do |audit|
attributes = audit.changes.inject({}) do |attrs, (name,
values)|
attrs[name] = values.first
attrs
end
changes.merge!(attributes.merge!(:version =>
audit.version))
yield changes if block_given?
end
block_given? ? result : changes
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
# Creates a new record in the audits table if applicable
def audit_create
write_audit(:create)
end
def audit_update
write_audit(:update) if changed?
end
def audit_destroy
write_audit(:destroy)
end
def write_audit(action = :update, user = nil)
self.audits.create :changes =>
@changed_attributes, :action => action.to_s, :user => user
end
# clears current changed attributes. Called after save.
def clear_changed_attributes
@changed_attributes = {}
end
# overload write_attribute to save changes to audited
attributes
def write_attribute_with_auditing(attr_name, attr_value)
attr_name = attr_name.to_s
if audited_attributes.include?(attr_name)
@changed_attributes ||= {}
# get original value
old_value = @changed_attributes[attr_name] ?
@changed_attributes[attr_name].first : self[attr_name]
write_attribute_without_auditing(attr_name, attr_value)
new_value = self[attr_name]
@changed_attributes[attr_name] = [old_value, new_value]
if new_value != old_value
else
write_attribute_without_auditing(attr_name, attr_value)
end
end
CALLBACKS.each do |attr_name|
alias_method "orig_#{attr_name}".to_sym, attr_name
end
def empty_callback() end #:nodoc:
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 the auditing callbacks 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
class_eval do
CALLBACKS.each do |attr_name|
alias_method attr_name, :empty_callback
end
end
write_inheritable_attribute :auditing_enabled, false
end
def enable_auditing
class_eval do
CALLBACKS.each do |attr_name|
alias_method attr_name, "orig_#{attr_name}".to_sym
end
end
write_inheritable_attribute :auditing_enabled, true
end
end
end
end
end
On Aug 26, 3:09 am, dougbeal <dougb...@gmail.com> wrote:
> What version of rails are you using?
cch: Rails 1.2.3
>Does the error go away if you
> turn of caching?
cch: acts_as_audited depends on caching and that's why it can only
work in Production Mode
> > > can help ?- Hide quoted text -
>
> - Show quoted text -