active scaffold nesting does not work with acts_as_audited

126 views
Skip to first unread message

CCH

unread,
Aug 25, 2007, 12:45:42 AM8/25/07
to ActiveScaffold : Ruby on Rails plugin
Hi

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

CCH

unread,
Aug 25, 2007, 6:03:24 AM8/25/07
to ActiveScaffold : Ruby on Rails plugin
Hi

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 ?


Lance Ivy

unread,
Aug 25, 2007, 11:28:41 AM8/25/07
to actives...@googlegroups.com
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? where does the audits table come into the mix?

dougbeal

unread,
Aug 25, 2007, 3:09:27 PM8/25/07
to ActiveScaffold : Ruby on Rails plugin
What version of rails are you using? Does the error go away if you
turn of caching?

-Doug

CCH

unread,
Aug 26, 2007, 1:18:21 PM8/26/07
to ActiveScaffold : Ruby on Rails plugin
Hi Lance

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

CCH

unread,
Aug 26, 2007, 1:19:59 PM8/26/07
to ActiveScaffold : Ruby on Rails plugin
HI Doug

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 -

Reply all
Reply to author
Forward
0 new messages