jahed.dev

Generating RSS/Atom Feeds for Individual Tags in a Middleman Blog

My blog can cover a wide range of topics which I organise using tags. How can I provide filtered RSS/Atom feeds so that my readers can subscribe to the tags that interests them?

I recently needed to provide tag filtered feeds for the Unruly Tech site which uses it to aggregate relevant posts from my blog with other blog-enabled developers at Unruly. Previously Tumblr provided this functionality but it was lost when I migrated to Middleman and static hosting.

It's pretty straight forward to do once you get your head around some of the template trickery that Middleman allows and boils down to two steps.

1. Create a feed.xml Template

Here's an example feed.xml.builder which is essentially a simpler version of my own Atom feed. It's using the Builder templating engine (hence .builder) to generate XML so you'll need to add gem "builder" to your project's Gemfile and rerun bundle install.

---
layout: false
---
root_url = "http://jahed.dev"
feed_url = URI.join(root_url, current_page.path)

if defined? tag_name
  articles = blog.tags[tag_name]
  page_url = URI.join(root_url, blog.controller.tag_pages.link(tag_name))
else
  articles = blog.articles
  page_url = URI.join(root_url, "#{blog.options.prefix.to_s}/")
end

xml.instruct!
xml.feed "xmlns" => "http://www.w3.org/2005/Atom" do
  xml.title [tag_name, "Jahed's Blog"].compact.join(" - ")
  xml.subtitle [tag_name, "Posts by Jahed"].compact.join(" ")
  xml.id page_url
  xml.link "href" => page_url
  xml.link "href" => feed_url, "rel" => "self"
  xml.updated(articles.first.date.to_time.iso8601) unless articles.empty?
  xml.author { xml.name "Jahed Ahmed" }

  articles[0..5].each do |article|
    article_url = URI.join(root_url, article.url)
    xml.entry do
      xml.title article.title
      xml.link "rel" => "alternate", "href" => article_url
      xml.id article_url
      xml.published article.date.to_time.iso8601
      xml.updated article.date.to_time.iso8601
      xml.author { xml.name "Jahed Ahmed" }
      xml.summary article.summary(500), :type => "html"
      xml.content article.body, "type" => "html"
    end
  end
end

This is essentially checking if a tag_name local variable has been passed into the template. If so it only shows posts under that tag using Middleman's blog.tag hash which maps tag names to an array of Articles.

A few other things to note in the template:

layout: false
Prevent Middleman from wrapping the template in any layouts you might have defined.
[tag_name, ...].compact.join
Used to conveniently concatenate the tag name to relevent text when it's available.
URI.join(root_url, ...)
Used to separate the feed from where it's hosted as some readers rely on absolute URLs to find resources.

You should customise the feed layout to suite your needs. Checkout out Atom's format to see what information you can provide. You can also use the RSS format, though the template will be completely different. There should be some example templates on GitHub/Google.

2. Generate Feeds for each Tag

Now that we have the feed template, we can wire it up with our blog's tags via the config.rb.

ready do
  blog.tags.each_key do |tag_name|
    destination_url = "/tags/#{tag_name}/feed.xml"
    template_url = "/feed.xml"
    proxy destination_url, template_url, :locals => { :tag_name => tag_name }
  end
end

Here we're going through every tag and using Middleman's proxy method to pass in tag_name and generate a feed using our template.

Pretty self explanatory. This needs to be in the ready block in the config.rb so that Middleman Blog has ran and populated blog.tags.

Also, since feed.xml.builder is in our project's sources, Middleman will automaticallt generate a feed.xml without a tag_name passed which will generate an unfiltered feed as we defined in our if-else block in the template.

3. You're done!

Just run bundle exec middleman build and you'll see your feeds built and ready to go. Run them through some feed readers like Firefox's built-in one, Feedly, W3C's Validator and others to make sure everything's valid.