Q&A: ActiveRecord Observers and You
14 comments Latest by grosser Thu, 02 Oct 2008 11:55:11 GMT
Yesterday, I wrote a short post titled, Observers Big and Small, about using Observers in your Rails applications.
The following questions were raised in the comments.
When should I use an Observer?
Eric Allam asks…
“Why not just use ActiveRecord callback hooks instead of Observers? Are Observers more powerful or is it just a matter of preference?”
Eric, this is an excellent question. I’d say that a majority of the time, using the ActiveRecord callbacks in your models is going to work for your situation. However, there are times that you want the same methods to be called through callbacks. For example, let’s take a recent problem that we used an observer to solve.
Graeme is working on implementing Ferret into a project that we’re developing for a client. With the use of Ferret, we can index and later search through content over several objects into a format that makes sense for our implementation goals. Each time an object is created and updated, we have to update our Ferret indexes to reflect these changes. The most obvious location that we can call our indexing methods is in each models’ callbacks, but this violates the DRY[1] principle. So, we created an Observer, which observes each of the models that need these methods to be called. In fact, as far as we’re concerned, the fact that we’re indexing some of its data, is none of its business. We only want our models to be concerned with that they’re designed to be concerned about. We may opt to change our indexing solution in the future and we’d just need to rethink that at the Observer level and not change anything about the business logic in our models.
This is the sort of scenario when using an Observer makes great sense in your application.
Logging from an Observer
Adam R. asks…
“I’d also like the ability to use the logger from within an observer, but that’s another issue.”
I assume that you are referring to the logger method? I always forget to even use that method. I do know that the following works just fine in an Observer.
class IndexObserver < ActiveRecord::Observer
observer Article, Editorial, BlogPost, ClassifiedAd
def after_save(model)
RAILS_DEFAULT_LOGGER.warn("Every single day. Every word you say. Every game you play. Every night you stay. I'll be watching you.")
# execute something fun
end
end
This will output to your log file without any problem.
This reminded me of when I used to want to log from Unit Tests.
(few minutes later)
Okay, I just attempted to use logger from an Observer and you’re right… it doesn’t currently work. There is a simple fix though, just extend ActiveRecord::Observer to add a logger method like so and require it in config/environment.rb (much like I did in with unit tests).
# lib/observer_extensions.rb
class ActiveRecord::Observer
def logger
RAILS_DEFAULT_LOGGER
end
end
This will give you a solution to that problem.
class FooObserver < ActiveRecord::Observer
observer Foo
def after_save(model)
logger.warn("I wonder if the #{address.class} knows that I've been watching it all along?")
end
end
Observers Spy for Us
Most often, I look at Observers as being the guys that I hire to spy on my models. I don’t want my models to know that they’re being spied on and I’d like to keep it that way. They don’t solve all of our problems and it’s easy to overuse them. However, I have found several cases that they made a lot of sense and most of those cases have been where we’ve had the same things occurring in our model’s callbacks.
If you have other questions related to Observers, feel free to let me know. If you’re already using Observers, perhaps you could post a comment and/or blog post response with an example of when and how you use Observers in your Rails applications.
Related Posts
1 Don’t Repeat Yourself
Enjoying the content? Be sure to subscribe to my RSS feed.






Thanks for this write-up. I appreciate it.
Last night after I posted the comment I took some time to look up Observers in the 2nd edition of the Rails book. There is an easier way to utilize the logger:
class FooObserver < ActiveRecord::Observer observe Foo end
Page 381
Probably would have been useful to mention the most common observer use-case—emails.
Your models shouldn’t care about emails. Not creating them, not sending them and sure as hell not caring if they fail or not.
Observers are perfect for sending mail after something happens. acts_as_authenticated / restful_authentication – case in point.
Ahh. I was under the mistaken impression that there was a one-to-one relationship between an observer and a model, and was not aware one observer could observe many models. Thanks for pointing that out. Time to look over some of my projects and see where I can use observers to dry up some code.
For anyone interested, here is an article on how to use Ferret: http://blog.whitewallweb.com/2007/08/08/full-text-database-search-using-%e2%80%98acts_as_ferret%e2%80%99-in-ruby-on-rails/
http://adqraeqfbewbw.host.com desk3 [url=http://adqsaeqfbewbw.host.com]desk4[/url] [link=http://adqaaeqfbewbw.host.com]desk6[/link]
Robby,
I can use Observer successfully with my own models. However, if the model is from a plugin (for example, the Comment model in acts_as_commentable) as in the following:
I get the following error on the line:
ArgumentError (A copy of NewsfeedObserver has been removed from the module tree but is still active!)
I have in the environment.rb:
It may be due to whether Rails encounter newsfeed_observer.rb or comment.rb (in acts_as_commentable) first. I am not sure. How can we go about making Observer work with models from plugins?
Thanks.
@ray: You probably solved it by now, but for anyone else… I ran into the same thing. Adding ‘unloadable’ to top of the model in the plugin made it work for me.
Darcy,
Thanks for posting this fix of adding unloadable. I ran into this same ArgumentError about two months ago, and just came back to try and solve the problem. I hate changing the model in my plugin though…
Eric
Is there way to do an Application observer that is create an observer that observes all models by default? I’m trying to create a modified_by and created_by system and I would prefer not to have to update my observer every time i added a model.
Robby,
I am a rails newbie struggling w/ this observer issue. I have an observer I want to use for audits.
I have this at the tail end of my environment.rb
AuditObserver.instanceputs "after instantiation #{AuditObserver.instance}"AuditObserver.instance.observed_classes.each {|x| puts x.name }All of these appear to work correctly. The right model classes are in the output.
However, my observer doesn’t fire at all. Every few runs I’ve seen it fire occasionally, but I am unable to find a pattern. Obviously this is not enough information, but I am not even sure how or what to look for. Any help or direction is appreciated. I am at a total loss.
Some highlights about the observer. It is observing multiple models and it is calling Audit.create (where Audit is another model object that stores the audit)
Vijay
Robby,
I am a rails newbie struggling w/ this observer issue. I have an observer I want to use for audits.
I have this at the tail end of my environment.rb
AuditObserver.instanceputs "after instantiation #{AuditObserver.instance}"AuditObserver.instance.observed_classes.each {|x| puts x.name }All of these appear to work correctly. The right model classes are in the output.
However, my observer doesn’t fire at all. Every few runs I’ve seen it fire occasionally, but I am unable to find a pattern. Obviously this is not enough information, but I am not even sure how or what to look for. Any help or direction is appreciated. I am at a total loss.
Some highlights about the observer. It is observing multiple models and it is calling Audit.create (where Audit is another model object that stores the audit)
Vijay
Oops. sorry for the double post. Got a little trigger happy :)
Oh, and one more thing. This started happening after I added the acts_as_soft_deletable plugin, which was the reason I added the AuditObserver.instance to the bottom of my environment.rb and didn’t use config.active_record.observers (which was working fine). I am on rails 2.0.2 w/ Ruby 1.8.3
My new observer did not work although tested, until i found out i forgot to add it to config…
Now i autoload my observers…
http://pragmatig.wordpress.com/2008/10/02/why-your-pretty-tested-observer-might-not-work/