# Decoupled observers for controllers?

**URL:** <https://www.ruby-forum.com/t/decoupled-observers-for-controllers/100827>\
**Category:** Rails\
**Created:** [August 1, 2006, 12:26pm UTC](https://www.ruby-forum.com/t/decoupled-observers-for-controllers/100827 "2006-08-01T12:26:57Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![david](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/d/aeb1de/32.png) [@david](https://www.ruby-forum.com/u/david)\
**Post date:** [August 1, 2006, 12:26pm UTC](https://www.ruby-forum.com/t/decoupled-observers-for-controllers/100827/1 "2006-08-01T12:26:57Z")

</div>

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.

---

<div class="post-metadata">

**Author:** ![david](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/d/aeb1de/32.png) [@david](https://www.ruby-forum.com/u/david)\
**Post date:** [August 1, 2006, 2:53pm UTC](https://www.ruby-forum.com/t/decoupled-observers-for-controllers/100827/2 "2006-08-01T14:53:27Z")

</div>

> 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.

---

<div class="post-metadata">

**Author:** ![david](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/d/aeb1de/32.png) [@david](https://www.ruby-forum.com/u/david)\
**Post date:** [August 1, 2006, 2:57pm UTC](https://www.ruby-forum.com/t/decoupled-observers-for-controllers/100827/3 "2006-08-01T14:57:44Z")

</div>

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.

---

<div class="post-metadata">

**Author:** ![david](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/d/aeb1de/32.png) [@david](https://www.ruby-forum.com/u/david)\
**Post date:** [August 1, 2006, 4:04pm UTC](https://www.ruby-forum.com/t/decoupled-observers-for-controllers/100827/4 "2006-08-01T16:04:12Z")

</div>

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?

---

<div class="post-metadata">

**Author:** ![david](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/d/aeb1de/32.png) [@david](https://www.ruby-forum.com/u/david)\
**Post date:** [August 1, 2006, 4:28pm UTC](https://www.ruby-forum.com/t/decoupled-observers-for-controllers/100827/5 "2006-08-01T16:28:42Z")

</div>

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

---

<div class="post-metadata">

**Author:** ![david](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/d/aeb1de/32.png) [@david](https://www.ruby-forum.com/u/david)\
**Post date:** [August 1, 2006, 5:18pm UTC](https://www.ruby-forum.com/t/decoupled-observers-for-controllers/100827/6 "2006-08-01T17:18:26Z")

</div>

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?

---

<div class="post-metadata">

**Author:** ![david](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/d/aeb1de/32.png) [@david](https://www.ruby-forum.com/u/david)\
**Post date:** [August 1, 2006, 5:33pm UTC](https://www.ruby-forum.com/t/decoupled-observers-for-controllers/100827/7 "2006-08-01T17:33:31Z")

</div>

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

---

<div class="post-metadata">

**Author:** ![david](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/d/aeb1de/32.png) [@david](https://www.ruby-forum.com/u/david)\
**Post date:** [August 3, 2006, 1:08pm UTC](https://www.ruby-forum.com/t/decoupled-observers-for-controllers/100827/8 "2006-08-03T13:08:42Z")

</div>

> 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

---

<div class="post-metadata">

**Author:** ![david](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/d/aeb1de/32.png) [@david](https://www.ruby-forum.com/u/david)\
**Post date:** [August 2, 2006, 4:14pm UTC](https://www.ruby-forum.com/t/decoupled-observers-for-controllers/100827/9 "2006-08-02T16:14:21Z")

</div>

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.
