Using Auditable in nested controllers.
[ Posted by James Harton ]
After yesterday releasing the initial version of Auditable I have added a few extra features, the most important of which is a Model.auditable? method so you can tell from other parts of the model whether you're dealing with a model that potentially has audit logs.
In my application I will be using a controller called audit_controller.rb which contains only the index method and will be nested below controllers for almost every model that is using Auditable. The first thing you need to do is set up your routes, my application has two controllers called remote_servers_controller and tasks_controller, both of which are auditable. We'll set up nested routes by mapping their resources, however we only want to route the index method because we don't want anyone to be able to change the audit logs. My config/routes.rb file looks like this:
ActionController::Routing::Routes.draw do |map|
map.resources :tasks do |controller|
controller.resources :audits, :only => :index
end
map.resources :remote_servers do |controller|
controller.resources :audits, :only => :index
end
map.connect ':controller/:action/:id'
map.connect ':controller/:action/:id.:format'
end
Next we need to create an audits_controller.rb file in app/controllers to handle these requests:
class AuditsController < ApplicationController
before_filter :find_auditable_parent
def index
@audits = @parent.audits.find(:all, :order => 'audits.created_at DESC')
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @audits }
end
end
protected
def find_auditable_parent
# Use find parent to dynamically figure out
# what the parent model for this request is
if self.find_parent
# Check if that model is auditable.
if @parent.class.auditable?
true
else
false
end
end
end
end
As you can see we access the parent model's audits method to limit the scope of our find so that we only show audits about that particular model. You'll notice that there is a before_filter applied to this controller calling a method called find_parent_auditable, which is what is used to verify that the @parent variable is auditable. find_parent_auditable also calls self.find_parent which is such a common pattern with nested resources that I wound up writing something generic and including it in my ApplicationController:
def find_parent
# Dynamically figure out and what the parent
# model is for a nested resources request.
params.each do |key,val|
if key[-3..-1] == '_id'
# parameter is potentially an FK field.
potential_model = key[0..-4]
if !potential_model.empty?
# only keep processing if the name is not
# empty.
begin
model = Object.const_get potential_model.camelize
if model.ancestors.member? ActiveRecord::Base
if val.to_i > 0
@parent = model.find(val.to_i)
true
else
# no point trying to clone the model if the
# id doesn't make sense.
false
end
else
# If the Class 'PotentialModel' is not a subclass of
# ActiveRecord::Base then return false.
false
end
rescue NameError
# If there is no Class by the name of 'PotentialModel'
# then return false.
false
end
end
end
end
end
As you can see this function iterates through params looking for variables like remote_server_id, it uses that to attempt to imply the name of the model. It checks the model to see if it has ActiveRecord::Base as an ancestor, in which case it populates @parent with the correct instance of the model and returns true, telling the before_filter that it can allow the request to proceed to the controller.
Now all you need to do is create app/views/audits/index.erb to display your audit logs.





