Publishing non-ActiveRecord objects in an Atom feed with Rails

Posted by Luke Francl
on Wednesday, October 01

Rails comes with a method called atom_feed that makes it really easy to publish an Atom feed for a list of ActiveRecord objects.

Here’s an example from the documentation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
atom_feed do |feed|
  feed.title("My great blog!")
    feed.updated((@posts.first.created_at))

    for post in @posts
      feed.entry(post) do |entry|
        entry.title(post.title)
        entry.content(post.body, :type => 'html')

        entry.author do |author|
          author.name("DHH")
        end
      end
    end
  end
end

For each <entry>, the Atom elements for <id>, <published>, <updated>, and <link> are automatically assigned based on the methods id, created_at, updated_at and polymorphic_url respectively.

However, I wanted to publish a non-ActiveRecord object in the feeds for Tumblon. Specifically, a group of photos.

multi-photo post example

Each of these elements can be specified directly, except <id>. So you could do something like this:

1
2
3
4
5
6
7
8
feed.entry(post, :url => my_custom_url, :published => post.some_date, :updated => post.some_other_date) do |entry|
  entry.title(post.title)
  entry.content(post.body, :type => 'html')

  entry.author do |author|
    author.name("DHH")
  end
end

The big problem with this is if you’re using a non-ActiveRecord object, it won’t have a stable id, and so the Atom feed’s <id> will change every time the feed is generated—leading to duplicate posts in feed readers. So you need to conjure up a stable id for your non-ActiveRecord object.

In my case, I created an stable id using my list of photos. I also added created_at and updated_at methods so I don’t have to specify them when creating the feed.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class MultiPhotoPost
  # truncated ...
  
  # override id so that the atom feed <id> doesn't change between runs
  def id
    created_at.to_i
  end
  
  def created_at
    @created_at ||= photos.min { |a, b| a.created_at <=> b.created_at }.created_at
  end
  
  def updated_at
    @updated_at ||= photos.max { |a, b| a.updated_at <=> b.updated_at }.updated_at
  end

end

With this in place, you’ll just need to specify the URL for the object (since polymorphic_url won’t work) when you create the feed <entry>.

Comments

Leave a response

  1. grosserOctober 02, 2008 @ 12:33 AM

    nice post, definetly worth bookmarking for futre use :)

    how about this ? @updated_at ||= photos.map(&:updated_at).max

  2. Luke FranclOctober 02, 2008 @ 12:43 PM

    grosser—ah, nice. That makes the methods much cleaner.