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.
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.
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):
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>
Given these two existing components (a model and an HTML partial for a photo), I set about writing my feed generator. It needed:
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:
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:
rfc822, which returns a string suitable for use inside the <pubDate> element of an RSS feed.xml << h(...) syntax directly appends some content to the XML output, attaching it to the currently active element (in this case, the <description>).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.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!
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.
| Attachment | Size |
|---|---|
| rss.zip | 71.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.