Easy RSS in Rails 2

A post by Peter Hollows about RSS and rails. Posted 9 months ago.

Of all the methods online for doing RSS in Rails, I’ve found this one to be the most: DRY, flexible, RESTful and extendable. This plugin-free approach is congruent with Rails 2 technique, follows the RSS 2.0 spec and passes W3C validation.

If you’re wanting more than RSS, Rails comes with a helper for creating Atom feeds, called AtomFeedHelper. Each to their own protocol, I prefer RSS because it’s lean.

How to make RSS feeds

Firtly, create a layout for our RSS feeds. We might make more down the track so we save repeating ourselves with this. In my ApplicationController I use layout :application but everyone has different names for their default layouts. I’ll assume you’ve named yours in this style. This layout will be for the RSS mime-type, and will use builder for the template creation so it’ll be called application.rss.builder.

# app/views/layouts/application.rss.builder
xml.instruct! :xml, :version => '1.0' 
xml.rss :version => '2.0', 'xmlns:atom' => 'http://www.w3.org/2005/Atom' do
  xml << yield
end

Next, change the controller. We’re RSS feeds for posts here so we’ll alter the PostsController. In the controller add RSS to the respond_to block:

# app/controllers/posts_controller.rb
def index
  @posts = Post.find(:all, :limit => 20, :order => 'created_at DESC')
  
  respond_to do |format|
    format.html
    format.rss # Add this line so we can respond in RSS format.
  end
end

Next comes the template, we name this index.rss.builder for the same reasons we named the layout.

# app/views/posts/index.rss.builder
xml.channel do
  # Required to pass W3C validation.
  xml.atom :link, nil, {
    :href => posts_url(:format => 'rss'),
    :rel => 'self', :type => 'application/rss+xml'
  }
  
  # Feed basics.
  xml.title             page_title
  xml.description       page_title
  xml.link              posts_url(:format => 'rss')
  
  # Posts.
  @posts.each do |post|
    xml.item do
      xml.title         post.title
      xml.link          post_url(post)
      xml.pubDate       post.created_at.to_s(:rfc822)
      xml.guid          post_url(post)
    end
  end
end

That’s it! You can find the feeds at http://localhost:3000/posts.rss.

How it works

The code is kept simple because we’re making use of Rails conventions to save us from declaring the obvious. Which layout, template and method to use are all handled by Rails routing and response code. Assuming the posts are resources we can reuse the current route for the feed like so:

# Get the URL of the posts in RSS format.
posts_path(:format => 'rss') # gives /posts.rss

Extras

Now you’ve got your basic RSS feed in place, it’s time for some extras. There is a lot you can do with RSS but here are my favorites.

Browser support

You can specify the feed for a page in the head of your HTML layout using:

<%= auto_discovery_link_tag(:rss, posts_url(:format => 'rss')) %>

Modern browsers will recognize this and let the user know RSS is supported at this location.

Feed icons

Pimp your new feeds using art from feedicons.com.

RSS sugar

We can add to the template above using optional extras from the RSS 2.0 spec.

# app/views/posts/index.rss.builder
xml.channel do
  # Required to pass W3C validation.
  xml.atom :link, nil, {
    :href => posts_url(:format => 'rss'),
    :rel => 'self', :type => 'application/rss+xml'
  }
  
  # Feed basics.
  xml.title             page_title
  xml.description       page_title
  xml.link              posts_url(:format => 'rss')
  
  # Posts.
  @posts.each do |post|
    xml.item do
      xml.title         post.title
      xml.link          post_url(post)
      xml.pubDate       post.created_at.to_s(:rfc822)
      xml.guid          post_url(post)
      
      # Assuming you have an author for posts.
      xml.author        "#{post.author.email} (#{post.author.full_name})"
      
      # ...and your posts have multiple tags (or categories).
      post.tags.each do |tag|
        xml.category    tag.name, :domain => tag_url(tag)
      end
    end
  end
end

Proper GUIDs (important)

The GUID is used by RSS readers to track which items the user has read. If this changes for a post every reader will forget it has been read. Please fix this!

A GUID shouldn’t change, we’re using the URL of the post for our GUID which is meant to be a permalink, but if we change the hostname, protocol or routes for our post this will change.

To keep your readers happy use something unique for the GUID. You don’t have to use a URL if you specify this with one more attribute in the XML.

# in app/views/posts/index.rss.builder
# Make your own random string using
# ./script/runner "puts ActiveSupport::SecureRandom.hex(20)"
xml.guid "[your string here]/posts/#{post.id}", :isPermaLink => false

Analytics

Google Feedburner lets you gather statistics on your posts and takes some of the load off your server. Once you have your Feedburner account and feed set up, leave your controller code as-is. You might want to redirect people to the Feedburner copy but then Feedburner can’t get at your feed itself. Instead change the location in the auto_discovery_link_tag to the Feedburner feed without redirecting.

GeoRSS, media etc.

RSS has some very handy extensions that some clients support. You can add location information to items using GeoRSS, you can add media such as mp3s to make podcasts using encapsulation and even video.

Here’s an example of GeoRSS.

# within your xml.item block
xml.geo :lat,  -43.526958 # latitude
xml.geo :long, 172.744217 # longitude

Feed us

There are people that feel you shouldn’t provide feeds to your readers because it’s a bare medium. I believe sites supporting extra formats are more accessible in general.

The truth is content providers have very little control over the delivery of their information once it’s out there. Why starve readers then? Feed those who come to the table and they will return.