HOWTO: Add basic audit trail.....

13 views
Skip to first unread message

ottercat

unread,
Mar 24, 2007, 2:02:29 PM3/24/07
to ActiveScaffold : Ruby on Rails plugin
For a current project, I had need for an audit trail. In the spirit
of giving back and in an effort, albeit inadequate, to give thanks for
active_scaffold, I'd like to share the audit trail.....


This documents how to create a basic audit trail facility.


1. Create a log_entry and log_level table: (here's the migration for
it)

class CreateLogEntries < ActiveRecord::Migration
def self.up
create_table :log_entries, :force => true do |t|
t.column :entry, :text
t.column :user, :string
t.column :created_at, :datetime
t.column :loglevel_id, :integer
end

create_table :loglevels do |t|
t.column :name, :string
end
[:debug, :error, :fatal, :info, :log, :warn].each do |r|
Loglevel.create(:name => r.to_s)
end
end

def self.down
drop_table :log_entries
drop_table :loglevels
end
end

2. Create model for loglevels :

class Loglevel < ActiveRecord::Base
has_many :global_settings
has_many :log_entries
end

3. Create model for log_entries:
class LogEntry < ActiveRecord::Base
validates_presence_of :entry, :created_by
belongs_to :loglevel
end

4. Now for some magick; We're going to automagickally create methods
to create log entries for each of the levels without having to specify
the level. Add the following to the top of the file:

class Object
# the following is after Josh Staiger: Metaprogramming method
closures in Ruby
# http://joshstaiger.org/archives/2006/metaprogramming.html
def lexdef(method_symbol, &block)
self.class.send :define_method, method_symbol, &block
end

end

That method extends Object and allows an object to easily define new
methods on the fly.

Now, within the LogEntry class, add the following:

# creates helper methods for us.... for each of the log levels
Loglevel.find_all.each do |l|
lexdef "create_#{l.name}_entry".to_sym do |creator, entry|
self.create(:loglevel_id => l.id,
:entry => entry,
:created_by => creator)
end
end

It goes through each of the loglevel records, and creates a method for
each one, such as create_debug_entry -- so that if you should add a
new record in the future, you won't have to write a method for it.

Also, it helps keep your code *DRY*.

4. Now add the loglevels controller:

class LoglevelsController < JmanagerController
active_scaffold :loglevel do |config|
[:create, :delete, :update].each do |action|
config.send(action).link.security_method = :admin?
end

end
end

I've added security, to keep people from modifying the table.

5. And now for the LogEntriesController :
class LogEntriesController < JmanagerController
active_scaffold :log_entry do |config|
config.columns.add [:created_at]
config.actions.exclude :create
config.actions.exclude :update
config.actions.exclude :delete
list.per_page = 50
list.sorting = {:created_at => :desc}
end
end

6. Entering the home stretch.... in your lib directory, create
action_overrides.rb :
module ActionOverrides

# override if you want to do anything prior to destruction
def before_destroy(record); end
# override if you want to do anything after destruction
def after_destroy(record); end

# overridden from ActiveScaffold::Actions::Delete
def do_destroy
@record = find_if_allowed(params[:id], 'delete')
before_destroy(@record)
@record.destroy
after_destroy(@record)
end
end

As of rev 267, there is not support for hooks to perform operations
before or after a delete action. This adds the functionality.

7. Additionally, in the lib, create create_log_methods.rb:
require_dependency "action_overrides"

class Object
# the following is after Josh Staiger: Metaprogramming method
closures in Ruby
# http://joshstaiger.org/archives/2006/metaprogramming.html
def lexdef(method_symbol, &block)
self.class.send :define_method, method_symbol, &block
end

end


module CreateLogMethods
include ActionOverrides

def creator
lexdef :before_create_save do |record|
LogEntry.create_info_entry(@session[:user],
"#{record.name}
#{record.class.to_s.downcase} created.")
end

lexdef :before_update_save do |record|
LogEntry.create_debug_entry(@session[:user],
"#{record.name}
#{record.class.to_s.downcase} updated: #{record.inspect}")
end

lexdef :before_destroy do |record|
LogEntry.create_info_entry(@session[:user],
"#{record.name}
#{record.class.to_s.downcase} deleted. All child records also
deleted.")
end
end
end

These will create (override) methods within your actions which will
log the actions which have been created.

8. Finally.... In the ApplicationController, application.rb, place
the following at the top of the file:
require_dependency "create_log_methods"
require_dependency "action_overrides"

and within the class, place these lines:

include CreateLogMethods
before_filter :creator


A before filter is used to add the methods because otherwise
active_scaffold was overwriting the methods.

There you have it.... An easy (and dry) audit trail.

ottercat

unread,
Mar 24, 2007, 2:04:27 PM3/24/07
to ActiveScaffold : Ruby on Rails plugin
TAKE II -- I'd been paranoid & thought it would not html-ize the code;
so here it is in its "true" form:

This documents how to create a basic audit trail facility.


1. Create a log_entry and log_level table: (here's the migration for
it)

class CreateLogEntries < ActiveRecord::Migration
def self.up
create_table :log_entries, :force => true do |t|


t.column :entry, :text
t.column :user, :string
t.column :created_at, :datetime
t.column :loglevel_id, :integer
end

create_table :loglevels do |t|
t.column :name, :string
end
[:debug, :error, :fatal, :info, :log, :warn].each do |r|

Loglevel.create(:name => r.to_s)
end
end

def self.down
drop_table :log_entries
drop_table :loglevels
end
end

2. Create model for loglevels :

class Loglevel < ActiveRecord::Base


has_many :global_settings
has_many :log_entries
end

3. Create model for log_entries:

class LogEntry < ActiveRecord::Base


validates_presence_of :entry, :created_by
belongs_to :loglevel
end

4. Now for some magick; We're going to automagickally create methods
to create log entries for each of the levels without having to specify
the level. Add the following to the top of the file:

class Object
# the following is after Josh Staiger: Metaprogramming method
closures in Ruby
# http://joshstaiger.org/archives/2006/metaprogramming.html
def lexdef(method_symbol, &block)
self.class.send :define_method, method_symbol, &block
end

end

That method extends Object and allows an object to easily define new
methods on the fly.

Now, within the LogEntry class, add the following:

# creates helper methods for us.... for each of the log levels
Loglevel.find_all.each do |l|
lexdef "create_#{l.name}_entry".to_sym do |creator, entry|

self.create(:loglevel_id => l.id,
:entry => entry,
:created_by => creator)
end
end

It goes through each of the loglevel records, and creates a method for
each one, such as create_debug_entry -- so that if you should add a
new record in the future, you won't have to write a method for it.

Also, it helps keep your code *DRY*.

4. Now add the loglevels controller:

class LoglevelsController < JmanagerController


active_scaffold :loglevel do |config|
[:create, :delete, :update].each do |action|
config.send(action).link.security_method = :admin?
end

end
end

I've added security, to keep people from modifying the table.

5. And now for the LogEntriesController :

class LogEntriesController < JmanagerController


active_scaffold :log_entry do |config|
config.columns.add [:created_at]
config.actions.exclude :create
config.actions.exclude :update
config.actions.exclude :delete
list.per_page = 50

list.sorting = {:created_at => :desc}
end
end

end


module CreateLogMethods
include ActionOverrides

include CreateLogMethods
before_filter :creator

> #http://joshstaiger.org/archives/2006/metaprogramming.html

> #http://joshstaiger.org/archives/2006/metaprogramming.html

Lance Ivy

unread,
Apr 24, 2007, 12:23:10 PM4/24/07
to actives...@googlegroups.com
I pasted this into the wiki: http://wiki.activescaffold.com/wiki/show/AuditTrail

>       LogEntry.create_info_entry (@session[:user],

shadoi

unread,
Apr 25, 2007, 5:05:03 PM4/25/07
to ActiveScaffold : Ruby on Rails plugin
I went a different route which is a bit easier I think:

1. Install the acts_as_versioned gem.
2. Create/run migrations for versions tables
3. Add updated_by and created_by ActiveRecord extension, use either:
a. http://www.pluitsolutions.com/2006/08/15/rails-auto-assign-created-by-and-updated-by/
b. http://livsey.org/2005/07/16/adding_created_by_and_updated_by_to_rails/
3. Create/run migration to add created_by and updated_by to
<model>_versions tables
4. Restart and test.

-s-

On Mar 24, 11:04 am, "ottercat" <otter...@gmail.com> wrote:
> TAKE II -- I'd been paranoid & thought it would not html-ize the code;
> so here it is in its "true" form:
>

> This documents how to create a basicaudittrail facility.

> #http://joshstaiger.org/archives/2006/metaprogramming.html
> def lexdef(method_symbol, &block)
> self.class.send :define_method, method_symbol, &block
> end
>
> end
>
> That method extends Object and allows an object to easily define new
> methods on the fly.
>
> Now, within the LogEntry class, add the following:
>
> # creates helper methods for us.... for each of the log levels
> Loglevel.find_all.each do |l|
> lexdef "create_#{l.name}_entry".to_sym do |creator, entry|

> On Mar 24, 2:02 pm, "ottercat" <otter...@gmail.com> wrote:
>

> > For a current project, I had need for anaudittrail. In the spirit


> > of giving back and in an effort, albeit inadequate, to give thanks for
> > active_scaffold, I'd like to share theaudittrail.....
>

> > This documents how to create a basicaudittrail facility.

> ...
>
> read more »

Reply all
Reply to author
Forward
0 new messages