Observer Fun

Where I share the code I just added to this site for email notifications on new comments and messages.

Ever wanted to know how to be notified by email when someone posted a comment on your home-brew blog system?

Prerequisites

You'll need: a working mail server already set up with your Rails application. Check out Step 1: The Setup in, (A Better) Contact Form in Rails.

Step 1: Generate the Mailer and Observer

./script/generate mailer Notifications new_comment

This will create app/models/notifications.rb with a single method: #new_comment.

./script/generate observer Event

This creates app/models/event_observer.rb.

Step 2: Educate Rails

Open config/environment.rb. Anywhere inside the Rails::Initializer.run block, add:

config.active_record.observers = :event_observer

Now Rails knows about the new observer we just created.

Step 3: The Only Real Programming Involved

Open app/models/event_observer.rb, right now it's just a blank class. Let's add some magic beans to it.

class EventObserver < ActiveRecord::Observer
  observe :comment

  def after_create(model)
    Notifications.deliver_new_comment(model)
  end
end

Okay, so we make it observe the Comment model with the #observe method, and add a method to be called after #create is called, which in turns calls #deliver_new_comment method in our mailer (a dynamic method that instantiates the Notifications class, calls #new_comment and delivers the email). That's a lot of calling.

In app/models/notifications.rb, you should have a single method called new_comment.

class Notifications < ActionMailer::Base
  def new_comment(sent_at = Time.now)
    subject    '[domain.ca] New Comment'
    recipients 'you@yourdomain.ca'
    from       'noreply@domain.ca'
    sent_on    sent_at
  end
end

Basically just fill in the method to your fancy. Refer to the Complete List of Action Mailer User-Settable Attributes on Rails Guides for information on what attributes you can set.

It's possible to use the record's data for the email, but without knowing the column information of your Comment model, it's difficult.

Open up app/views/notifications/new_comment.erb. It could look like...

A new comment has been created on your awesome website! Check it out now at <%= link_to "your website", root_url %>!

And yeah...

So, if everything is working correctly, every time someone creates a comment, you should receive an email notifying you.

Make it More Complex

So you want to include more information in that email, and you also want to observe your Message model to see when someone flames you? That's easy.

Assuming your database is structured as follows:

comments:
  author: string
  author_email: string
  content: text

messages:
  name: string
  email: string
  subject: string
  message: text

In app/models/event_observer.rb:

class EventObserver < ActiveRecord::Observer
  observe :comment, :message

  def after_create(model)
    case model
      when Comment then Notifications.deliver_new_comment(model)
      when Message then Notifications.deliver_new_message(model)
    end
  end
end

In app/models/notifications.rb:

class Notifications < ActionMailer::Base
  def new_comment(comment, sent_at = Time.now)
    subject    '[domain.ca] New Comment'
    recipients 'you@yourdomain.ca'
    from       'noreply@domain.ca'
    sent_on    sent_at

    body       :author       => comment[:author],
               :author_email => comment[:author_email],
               :content      => comment[:content]
  end

  def new_message(message, sent_at = Time.now)
    subject    '[domain.ca] New Message'
    recipients 'you@yourdomain.ca'
    from       'noreply@domain.ca'
    sent_on    sent_at

    body       :name    => message[:name],
               :re => message[:subject],
               :email   => message[:email],
               :message => message[:message]
  end
end

In app/views/notifications/new_comment.erb:

<%= @author %> <<%= @author_email %>> wrote a new comment:

---
<%= @content %>
---

In app/views/notifications/new_message.erb:

<%= @name %> <<%= @email %>> wrote a message:

Re: <%= @re %>

---
<%= @message %>
---

Boom! This is, more or less, the same code I use.

Filed in Tutorials and The Site.