Decoupled observers for controllers?

In the Rails Recipes book the recipe “Keeping track of who did what”
explains how to do decoupled observing of models.

In my application I’d like to do a similar thing but watching the
controllers. For example, when somebody hits the login method of the
Security controller, I’d like to make a note of it. In this case I
could observe the User model and watch for authenticate?, but in the
case of some other actions in the application (e.g. logging out) I
cannot see how to observe the model and get the same results.

Is it possible to observe the controllers in any way?

Thanks.

Is it possible to observe the controllers in any way?

Use a filter/observer monster. See the cache sweeper implementation
for example.

It’s basically an observer with filter callbacks.

In your case, you could store the controller/action names in an
intance variable during the after_filter, then use before_save to set
stuff in the record.

On 2006-08-01, at 09:49 , Caio C. wrote:

In your case, you could store the controller/action names in an
intance variable during the after_filter

That would be a before_filter, or else it’ll not be set by the time
the record is saved.

Caio C. wrote:

Is it possible to observe the controllers in any way?

Use a filter/observer monster. See the cache sweeper implementation
for example.

It’s basically an observer with filter callbacks.

In your case, you could store the controller/action names in an
intance variable during the after_filter, then use before_save to set
stuff in the record.

This is fine, but what about the case where I want to record (for
example) the user choosing “logout”. In this case, there is no model to
observe with before_* or after_*.

The cache_sweeper example I have seen observes one model and only
certain events, using the before and after filters. I would like to
observe multiple controllers in my application.

Is it better to just put the logging statements in the code, or can this
be done in a decoupled way?

On 2006-08-01, at 11:04 , David wrote:

this
be done in a decoupled way?

True. So you just need a filter.

I assume you keep track of the current user in the session. So:

after_filter :log_action

def log_action
User.find(sesson[:user_id]).actions.create(
:controller => controller_path,
:action => action_name)
end

Caio C. wrote:

On 2006-08-01, at 11:04 , David wrote:

this
be done in a decoupled way?

True. So you just need a filter.

I assume you keep track of the current user in the session. So:

after_filter :log_action

def log_action
User.find(sesson[:user_id]).actions.create(
:controller => controller_path,
:action => action_name)
end

That’s great, thank you. I guess I can use that in the application
controller then filter out only the actions I am interested in saving
information about.

Should I add the after_filter to the application controller and then
observe it from the cache sweeper, or just add both to the controller?

Do you have any advice on how to manage the mapping of which actions I
wish to save audit information about? A hash of their names perhaps?

On 2006-08-01, at 12:18 , David wrote:

That’s great, thank you. I guess I can use that in the application
controller then filter out only the actions I am interested in saving
information about.

Yea, I intended it to be in the application controller.

Should I add the after_filter to the application controller and then
observe it from the cache sweeper, or just add both to the controller?

You don’t need to observe anything, do you?

I think you can handle it all from the controller.

I messed up in my previous suggestion, for a while I thought you
asked about something else: I’d use that observer/filter pattern to
set a user_id field whenever a record is saved.

Do you have any advice on how to manage the mapping of which actions I
wish to save audit information about? A hash of their names perhaps?

A hash is fine. You can go fancy with a DSL class method, so:

In application.rb:

def self.audit_actions *actions
write_inheritable_array :audit_actions, [actions].flatten.compact
end

Elsewhere:

class SecurityController < …
audit_actions :login, :logout, :change_password

And my log_action, modified:

def log_action
return unless read_inheritable_attribute(
:audit_actions).include?(action_name)
User.find(sesson[:user_id]).actions.create(
:controller => controller_path,
:action => action_name)
end

In my application I’d like to do a similar thing but watching the
controllers. For example, when somebody hits the login method of the
Security controller, I’d like to make a note of it. In this case I
could observe the User model and watch for authenticate?, but in the
case of some other actions in the application (e.g. logging out) I
cannot see how to observe the model and get the same results.

Is it possible to observe the controllers in any way?

I use a before filter.

I set up a model for activities, which keeps the user, an activity (some
text) and a created_on date.

/app/models/activity.rb
class Activity < ActiveRecord::Base
belongs_to :user
end

/app/controllers/application.rb,

Log requests to the activity log

protected
def activity_log(activity_text=request.env[‘REQUEST_URI’])
if logged_in?
u = current_user
u.activities.create( :description => activity_text)
end
true
end

Then, in any controller I want to log
before_filter :login_required
before_filter :activity_log

In the special case of the login controller, in order to catch the login
event, I have:
after_filter :activity_log

Note that I only care about activities of logged in users, and my code
accounts for that. I suppose one could log all activities to a ‘guest’
account should they so desire.

Regards,
Rich

Caio C. wrote:

A hash is fine. You can go fancy with a DSL class method, so:

I really like this idea, thank you. It is very neat.

In application.rb:

def self.audit_actions *actions
write_inheritable_array :audit_actions, [actions].flatten.compact
end

No errors here.

And my log_action, modified:

def log_action
return unless read_inheritable_attribute(
:audit_actions).include?(action_name)
User.find(sesson[:user_id]).actions.create(
:controller => controller_path,
:action => action_name)
end

Here I’m getting:

undefined method `read_inheritable_attribute’ for
SecurityController:0xb742fe68

I’ve googled around and read_inheritable_attribute seems to have been in
rails for ages, so my web application should definitely have it (and
obviously write_inheritable_array seems to be working).

Am I missing something here? The code is:

def log_action
return unless
read_inheritable_attribute(:audit_actions).include?(action_name)

logger.info "THIS ACTION SHOULD BE LOGGED"

end

For the moment I’m just using logger.info, I’ll obviously put it in the
DB long term.

Thanks.

This forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.

| Privacy Policy | Terms of Service | Remote Ruby Jobs