Adding Hooks to Table Events

Problem

You want to run some code whenever a database row is added, updated, or deleted. For instance, you might want to send out email whenever a new blog post is created.

Solution

For Og, use the aspect-oriented features of Glue::Aspect. You can use its before and after methods to register code blocks that run before or after any Og method. The methods you e most likely to wrap are og_insert, og_update, and og_delete.

In the following code, I take the BlogPost class first defined in Recipe 13.12, and give its og_insert method an aspect that sends out email:

require cookbook_dbconnect require og require glue/aspects class BlogPost property :title, :content, String after :on => :og_insert do |post| puts %{Sending email notification of new post "#{post.title}"} # Actually send the email here… end end og_connect post = BlogPost.new post.title = Robots are taking over post.content = Think about it! When was the last time you saw another human? post.save! # Sending email notification of new post "Robots are taking over"

This technique works with ActiveRecord as well (since aspect-oriented programming is a generic technique), but ActiveRecord defines two different approaches: callbacks and the ActiveRecord::Observer class.

Any ActiveRecord::Base subclass can define a number of callback methods: before_find, after_save, and so on. These methods run before or after the corresponding ActiveRecord methods. Heres an callback-based ActiveRecord implementation of the Og example, running against the blog_post table first defined in Recipe 13.11. If you ran the previous example in a session, quit it now and start a new session.

require cookbook_dbconnect activerecord_connect class BlogPost < ActiveRecord::Base def after_create puts %{Sending email notification of new blog post "#{title}"} # Actually send the email here… end end post = BlogPost.create(:title => Robots: Gentle Yet Misunderstood, :content => Popular misconceptions about robERROR 40) # Sending email notification of new blog post "Robots: Gentle Yet Misunderstood

Discussion

ActiveRecords callback interface is simple, but its got a big disadvantage compared to Ogs. You can attach multiple aspects to a single method, but you can only define a callback method once.

This makes little difference when you only want the callback method to do one thing. But suppose that in addition to sending email whenever a blog post is created, you also want to notify people of new posts through an instant messenger client, and to regenerate static syndication feeds to reflect the new post.

If you used a callback, youd have to lump all of that code together in after_create. With aspects, each piece of functionality can go into a separate aspect. Its easy to add more, or to disable a single one without affecting the others. Aspects keep auxilliary code from cluttering up your core data classes.

Fortunately, ActiveRecord provides a strategy other than the callback methods. You can define a subclass of ActiveRecord::Observer, which implements any of the callback methods, and use the observe decorator to attach it to the classes you want to watch. Multiple Observers can watch a single class, so you can split up the work.

Heres a third example of the email notification code. Again, start a new session if you e following this recipe in irb.

require cookbook_dbconnect activerecord_connect class BlogPost < ActiveRecord::Base end class MailObserver < ActiveRecord::Observer observe BlogPost def after_create(post) puts %{Sending email notification of new blog post "#{post.title}"} # Actually send the email here. end end ActiveRecord::Base.observers = MailObserver post = BlogPost.new(:title => "ERROR 40", :content => "ERROR ERROR ERROR ERROR ERROR") post.save # Sending email notification of new blog post "ERROR 40"

Note the call to ActiveRecord::Base.observers=. Calling this method starts the observer running. You can call ActiveRecord::Base.observers= whenever you need to add one or more Observers. Despite the implication of the method name, calling it twice won overwrite one set of observers with another.

In a Rails application, observers are traditionally started by putting code like the following in the environment.rb file:

# environment.rb config.active_record.observers = MailObserver

When working with ActiveRecord, if you want to attach an Observer to a specific ActiveRecord class, you can name it after that class: for instance, BlogPostObserver will automatically observe the BlogPost class. Obviously, this only works for a single Observer.

See Also

Категории