Displaying a feed within your Rails app is pretty easy. It turns out caching that RSS feed is pretty easy too. I recently added cached RSS feeds to an app and had a few tiny hoops to jump through, so I thought I’d document them here for anyone else looking to solve the same problem.

Caching is easy. Cache invalidation is hard. For an RSS feed, we’d have to ping the feed to determine if any data had changed. Why bother? Instead of fetching the RSS feed on every single page request, let’s cache it for a fixed period of time.

The timed_fragment_cache plugin adds a duration option to the Rails cache method, allowing you to specify how long to cache a fragment of code. So, get it!

> ./script/plugin install git://github.com/GeorgePalmer/timed_fragment_cache.git

To integrate that cache method into a page, it might look like this:

<%- cache "statulous_blog", (Time.now + 60.minutes) do -%>
  <%= render_rss_feed("http://example.com/rss.xml") %>
<%- end -%>

The render_rss_feed helper method looks like this:

require 'rss/1.0'
require 'rss/2.0'
require 'open-uri'
require 'socket'
module ApplicationHelper
 
  def render_rss_feed(url)
    content = ""
    open(url, 0) do |s| content = s.read end
    feed = RSS::Parser.parse(content, false)
    @link = feed.channel.link
    @title = feed.channel.title
    @items = feed.channel.items[0..4] # just use the first five items
    render :partial => 'home/rss_view'
  end
 
end

And from there, pretty up that feed however you like. Here I’m just using the link, title and date:

<h3><a href='<%= @link %>'><%= @title %>:</a></h3>
<small>The most recent 5 entries:</small><br/>
<% @items.each do |item| %>
  <li><strong><a href='<%= item.link %>'><%= item.title %></a>
	</strong> - <small>(<%= item.date.strftime('%m/%d/%Y') %>)</small>
<% end %>

private method `gsub’ called for #

If you are using Rails 2+ and you try to get fancy with that duration specification, using something like 30.minutes.from_now, you’ll end up getting that gsub error. Here is why:

%ruby script/console
Loading development environment (Rails 2.1.0)
>> (Time.now + 30.minutes).class
=> Time
>> 30.minutes.from_now.class
=> ActiveSupport::TimeWithZone

Note: 30.minutes.from_now.time will result in a Time class, and will work equally as well as (Time.now + 30.minutes). It’s up to you which of those two are more readable and intention revealing.

Caution: Feed parsing is hard

The best part about feed standards is that there are so many to choose from! The code above is trivial, and will only handle feeds that fit that particular convention. Since I’m consuming feeds that I am also responsible for creating, I can opt for this simplest thing that could possibly work approach.

As soon as you start consuming several feeds, you’ll start to see just how diverse these feeds can be. Empty channels, posts without titles, non-unique identifiers… these are just the beginning. I’d love to hear what robust feed parsers folks are using in Ruby. The best feed parser I know of, and that some Rubists even use, is Universal Feed Parser, a Python library with 3k unit tests. Good luck!



Discussion, links, and tweets

Follow me on Twitter for more musings from the ether.

© 2007-2015 Brian Doll