Permalink
Browse files

Add RSS feed to posts

This displays an rss feed for posts at blog.edwardloveall.com/rss. I
used this for the specification:
https://cyber.law.harvard.edu/rss/rss.html

Of note, the post content is converted from markdown to html, including
syntax highlighting, which seems to work in my limited testing of RSS
readers.
  • Loading branch information...
edwardloveall committed Aug 24, 2016
1 parent c1f2de1 commit fd280c6ad1685e643b8188aff74dd4d294d8dd06
@@ -4,6 +4,11 @@ class PostsController < ApplicationController
def index
page = params[:page] || 1
@posts = Post.newest_first.page(page).per(10)
+
+ respond_to do |format|
+ format.html
+ format.rss { render :index, layout: nil }
+ end
end
def show
@@ -0,0 +1,20 @@
+xml.instruct! :xml, version: '1.0'
+
+xml.rss version: '2.0' do
+ xml.channel do
+ xml.title title
+ xml.link root_url(subdomain: 'blog') + 'rss'
+ xml.lastBuildDate @posts.first.created_at
+ xml.language 'en-US'
+
+ @posts.each do |post|
+ xml.item do
+ xml.title post.title
+ xml.link post_url(post.slug, subdomain: 'blog')
+ xml.description convert_markdown(post.body)
+ xml.pubDate post.created_at
+ xml.guid post.guid, isPermalink: false
+ end
+ end
+ end
+end
View
@@ -1,6 +1,7 @@
Rails.application.routes.draw do
constraints(subdomain: /blog.*/) do
get '/', to: 'posts#index'
+ get '/rss', to: 'posts#index', defaults: { format: :rss }
get '/:slug', to: 'posts#show', as: :post
resources :posts, only: [:index]
end
@@ -8,32 +8,42 @@
end
describe 'GET #index' do
- it 'assigns all posts as @posts' do
- post = create(:post)
+ context 'in html format' do
+ it 'assigns all posts as @posts' do
+ post = create(:post)
- get :index
+ get :index
- expect(assigns(:posts)).to eq([post])
- end
+ expect(assigns(:posts)).to eq([post])
+ end
- context 'pagination' do
- it 'returns maximum 10 posts' do
- create(:post)
- posts = create_list(:post, 10)
- posts.reverse!
+ context 'pagination' do
+ it 'returns maximum 10 posts' do
+ create(:post)
+ posts = create_list(:post, 10)
+ posts.reverse!
- get :index
+ get :index
- expect(assigns(:posts)).to eq(posts)
- end
+ expect(assigns(:posts)).to eq(posts)
+ end
- it 'returns posts from a page offset' do
- post = create(:post)
- create_list(:post, 10)
+ it 'returns posts from a page offset' do
+ post = create(:post)
+ create_list(:post, 10)
- get :index, page: 2
+ get :index, page: 2
- expect(assigns(:posts)).to eq([post])
+ expect(assigns(:posts)).to eq([post])
+ end
+ end
+ end
+
+ context 'in xml format' do
+ it 'uses no base layout' do
+ get :index, format: :rss
+
+ expect(response).not_to render_template(:blog)
end
end
end
View
@@ -2,6 +2,6 @@
factory :post do
title 'A Scary Story'
body 'It was a dark and stormy night...'
- slug { |n| "a-scary-story-#{n}" }
+ sequence(:slug) { |n| "a-scary-story-#{n}" }
end
end
@@ -0,0 +1,84 @@
+require 'rails_helper'
+
+RSpec.describe 'Posts requests' do
+ it 'returns an rss response' do
+ create(:post)
+
+ get feed_url
+
+ expect(response.content_type).to eq(Mime::Type.lookup_by_extension(:rss))
+ end
+
+ it 'has channel and meta attributes' do
+ post = create(:post)
+
+ get feed_url
+
+ rss = xml[:rss]
+ channel = rss[:channel]
+
+ expect(rss[:version]).to eq('2.0')
+ expect(channel[:title]).to eq('Edward Loveall')
+ expect(channel[:link]).to eq(feed_url)
+ expect(channel[:lastBuildDate]).to eq(post.created_at.to_s)
+ expect(channel[:language]).to eq('en-US')
+ end
+
+ it 'has entry attributes' do
+ create(:post)
+ post = create(:post)
+ post_body = MarkdownRenderer.to_html(post.body).html_safe
+
+ get feed_url
+
+ entry = xml[:rss][:channel][:item].first
+
+ expect(entry[:title]).to eq(post.title)
+ expect(entry[:link]).to eq(post_url(post.slug, subdomain: 'blog'))
+ expect(entry[:description]).to eq(post_body)
+ expect(entry[:pubDate]).to eq(post.created_at.to_s)
+ expect(entry[:guid]).to eq(post.guid)
+ end
+
+ it 'reports the guid as a non-permalink' do
+ create(:post)
+
+ get feed_url
+
+ xml = Nokogiri::XML(response.body)
+ guid = xml.at('guid')
+
+ expect(guid.attribute('isPermalink').value).to eq('false')
+ end
+
+ it 'returns a 304 when there are no new posts' do
+ create(:post)
+ get feed_url
+ headers = { 'HTTP_IF_NONE_MATCH' => response.etag }
+
+ get feed_url, {}, headers
+
+ expect(response).to have_http_status(:not_modified)
+ end
+
+ it 'returns a 200 when there are new posts' do
+ Timecop.travel(Date.yesterday)
+ create(:post)
+ get feed_url
+ headers = { 'HTTP_IF_NONE_MATCH' => response.etag }
+ Timecop.return
+ create(:post)
+
+ get feed_url, {}, headers
+
+ expect(response).to have_http_status(:ok)
+ end
+
+ def xml
+ Hash.from_xml(response.body).deep_symbolize_keys
+ end
+
+ def feed_url
+ "#{root_url(subdomain: 'blog')}rss"
+ end
+end

0 comments on commit fd280c6

Please sign in to comment.