Skip to content
This repository has been archived by the owner on Feb 18, 2024. It is now read-only.

Commit

Permalink
Add RSS feed to posts
Browse files Browse the repository at this point in the history
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 fd280c6
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 19 deletions.
5 changes: 5 additions & 0 deletions app/controllers/posts_controller.rb
Expand Up @@ -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
Expand Down
20 changes: 20 additions & 0 deletions app/views/posts/index.rss.builder
@@ -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
1 change: 1 addition & 0 deletions config/routes.rb
@@ -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
Expand Down
46 changes: 28 additions & 18 deletions spec/controllers/posts_controller_spec.rb
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion spec/factories/posts.rb
Expand Up @@ -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
84 changes: 84 additions & 0 deletions spec/requests/post_requests_spec.rb
@@ -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.