Ruby Tuesday: RSS feeds in Rails

Yesterday, I mentioned I have added RSS feed capabilities to FlickrLilli. I thought it might be worth sharing my experiences of this, as it took me a good couple of hours to iron out the wrinkles and get a valid feed.

Update: Thanks to Thomas Hurst (see comments) for pointing out that I can just use the output from the existing partial and escape it again.

Existing components

I had a lot of code for fetching photos from Flickr and displaying them as HTML: I wanted to reuse these as much as possible. The examples below simplify, as FlickrLilli is a special case and doesn't have a database back-end; for the example, I have assumed a photo database which stores Flickr-like photo objects.

The model

FlickrLilli doesn't use ActiveRecord, but has objects corresponding to photos from Flickr. For the purposes of this example, let's assume a photo actually corresponds to a table in the database called photos, and has a model that looks like this:

class Photo < ActiveRecord::Base
  has_and_belongs_to_many :tags
  belongs_to :owner
  
  # Show tag names in a human-readable string.
  # (Thanks to Tim [see comments] for reminding me of this syntax.)
  def tag_str
    tags.map(&:name).join(", ")
  end
end

And that the model supplies these attributes (which mirrors Flickr metadata on a photo):

  • title
  • description
  • date_taken
  • image_url (the URL to the actual photo graphic file)
  • tags (returns an array of Tag model objects)
  • licence
  • owner (an instance of an Owner model object, with a username and homepage URL)

The standard partial for rendering a photo

FlickrLilli uses a single partial to render a photo as HTML in different contexts (app/views/main/_metadata.rhtml). Paraphrasing a bit, this looks like:

<h2><%=h photo.title %></h2>
<p><%= image_tag(photo.image_url) %></p>
<p><strong>Tags:</strong> <%=h photo.tag_str %></p>
<div class="photo_details">
<p><strong>Licence:</strong> <%= photo.licence %></p>
<p><strong>Date and time taken:</strong> <%= photo.date_taken %></p>
<p><strong>Owner:</strong> <%= link_to photo.owner.username, photo.owner.homepage %></p>
<p><strong>Description:</strong><br/>
<%=h photo.description %>
</p>

The RSS feed generator

Given these two existing components (a model and an HTML partial for a photo), I set about writing my feed generator. It needed:

  1. A controller action to manage the feed
  2. An RXML template for rendering it (RXML uses Builder to generate XML output for controller actions)
  3. A route which makes it convenient to download the raw feed XML as an XML file with a .xml suffix

The controller action for the RSS feed

This was pretty quick to write. Again, using a simplified version for the purposes of exposition, I added the action to my MainController class (in app/controllers/main_controller.rb):

def rss
  # Get the 10 most recent photos
  @photos = Photo.find :all, :limit => 10, :order => 'date_taken DESC'
  # Title for the RSS feed
  @feed_title = "10 most recent photos"
  # Get the absolute URL which produces the feed
  @feed_url = "http://" + request.host_with_port + request.request_uri
  # Description of the feed as a whole
  @feed_description = "10 most recent photos"
  # Set the content type to the standard one for RSS
  response.headers['Content-Type'] = 'application/rss+xml'
  # Render the feed using an RXML template
  render :action => 'rss', :layout => false
end

There are a couple of key things to note here:

  1. The Content-Type header should be set properly for the response.
  2. The RXML template needs to be rendered without a layout, otherwise you get XML wrapped in the controller's layout.

The feed XML template

This uses the RXML template provided by Rails to generate the XML document. There is an example in the Rails documentation, but as far as I can see it doesn't produce a properly-valid feed, as it lacks an <?xml version="1.0"?> declaration. Here's my template, which went into app/views/main/rss.rxml:

xml.instruct!
xml.rss("version" => "2.0", "xmlns:dc" => "http://purl.org/dc/elements/1.1/") do
  xml.channel do
    xml.title @feed_title
    xml.link @feed_url
    xml.description @feed_description
    xml.language "en-gb"

    for photo in @photos
      xml.item do
        xml.pubDate photo.date_taken.rfc822
        xml.title h(photo.title)
        xml.link photo.image_url
        xml.guid photo.image_url
        xml.description do
          xml << h(render(:partial => 'metadata', :locals => {:photo => photo}))
        end
      end
    end
  end
end

Things of note:

  • A guid element is added for each item. This enables blog readers to uniquely identify items, meaning it can detect which items in a feed are new and which have already been seen. In this case, I'm just using the image URL; in the case of FlickrLilli, I use the URL of the photo detail page on FlickrLilli, which ensures the feed only shows items which are new, not updates to existing items.
  • The Time class in Ruby provides a handy instance method called rfc822, which returns a string suitable for use inside the <pubDate> element of an RSS feed.
  • The xml << h(...) syntax directly appends some content to the XML output, attaching it to the currently active element (in this case, the <description>).
  • The partial is rendered using the standard render method, passing the photo in as a local. The whole lot is then filtered through the h method to escape all the HTML. This will result in some parts of the HTML being "double escaped"; however, when decoded by the reader at the other end, it should be proper single-escaped HTML again.

A nice route

Applications like Firefox look at the filename suffix of a downloaded file to determine how to handle it. If we make the RSS feed available at a URL like http://localhost/rss, the downloaded file will be called rss, and some applications won't be able to work out what to do with it.

So it gets properly treated as an XML file, I add a custom route to config/routes.rb:

map.connect '/rss.xml', :controller => 'main', :action => 'rss'

And that's it!

Bonus sample code

Because I'm feeling generous, and because I wanted to check all the above actually worked, I put together a really simple Rails application which fleshes this out and includes a sample SQLite database and migrations. It's attached below. To run it, just use script/server, then browse to http://localhost:3000/rss.xml. When I tested it, it produced a valid RSS feed.

AttachmentSize
rss.zip71.93 KB

Comments

How do you add belongs_to associations into builder

How would you do a belongs_to association. For example if a person is in a photo and the photo could contain many people how do you display person.photo.title with xml builder. Displaying the data from a has_many relationship is simple using for person in @photo.people but for a belongs_to relationship I cant figure out how to do @person.photo.title(obviously this isn't working for me) in xml builder. This example is a little strange but for my purposes this is the type relationship i'm trying to display in xml. any ideas?

Assumption: person only shows up in one picture.

class Person < ActiveRecord::Base
belongs_to :photo
end

class Photo < ActiveRecord::Base
has_may :people
end

Thanks a lot for the example.

Thanks a lot for the example.

You're welcome, glad it was

You're welcome, glad it was useful.

What about authentication?

In my site a user has to login. I've made it work so that the RSS feed is for them, but I'm not sure how to get them to be able to loggin before hand. Any ideas? I'm just using basic salt/md5 hashing and sessions to store the user.

The simplest approach would

The simplest approach would be to give each user an authentication token (e.g. a sha1 hash of their username + password + salt) which they append to the RSS URL to get an authenticated feed. The RSS action only returns the feed if the token is present. That would probably do it.

h not necessary

You don't need to use the h function here. Stuff you put in is automatically encoded.

Thnks Helps me a lot

Hey thank u very much this helps me a lot.

Glad you found it useful.

Glad you found it useful. Happy RSS-feeding!

My rssfeed is not getting updatde

Hi i have adding rssfeed in my browser from my application. If add new updates those updates are not getting updated in my rssfeed...
Can you say me whats going wrong..

Thanks in advance...

Not sure about that, as the

Not sure about that, as the source for the RSS feed comes from the database. Are you caching db objects?

Great Post

This was a great example. Thank you very much.

Do you need to parse the date? I'm just using
myobj.created_at.rfc822

You're quite right. I've

You're quite right. I've included your refinement above.

thank you.

thany You.
i'm korean.

i find rss feed make. examples. :D
you goood job :D thany you

You're welcome, glad it was

You're welcome, glad it was helpful.

You tip to include a partial

You tip to include a partial in the description of a post is fantastic. I love it.

Hang on a minute: you're a

Hang on a minute: you're a tricksy spambot, aren't you? Go on, admit it.

html_escape e partial

Thanks for your hint to include a partial in the description of the post!

Bye

You're welcome. Glad it was

You're welcome. Glad it was useful.

def tag_str

tags.map(&:name).join(", ") ?

Thanks. That bit of

Thanks. That bit of shorthand is something I'm not very familiar with. I knew there had to be a better way!

Symbol#to_proc

The Symbol#to_proc hack - not (yet) in Ruby, but there in Rails and other projects.

Half a line is better than 4 :)

Don't you want to double-escape?

It's escaped so the HTML doesn't get in the way of the RSS markup; what's rendered to the user is then the HTML embedded in the description, after it's been parsed as XML into the HTML fragment. Surely by not double-escaping your actual content, that markup gets mangled just as if you'd served it directly to a browser?

Hmmm. I think you might well

Hmmm. I think you might well be right, there. Maybe I could just use the existing partial as is, and escape all of its content.