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.
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
> LogEntry.create_info_entry (@session[:user],
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 »