Sinatra, Heroku and Superfeedr

Sinatra, Heroku and Superfeedr

This is a short tutorial on how to deploy an Sinatra web app that uses Superfeedr to Heroku. This app provides a very simple home page that lists the latests entries of some of your favorites sites. It’s greatly inspired by the awesome start.io from the awesome Peter Vidani and Jacob Bijani

Set up

We will use Sinatra, the Rack Superfeedr gem, as well as Twitter’s bootstrap for the layout, because I suck at making things shinny.

Let’s start first by creating the application on Heroku.

Create the repo:

mkdir start-page && cd start-page
 git init
 git add .
 git commit -m "init"

Create the application on Heroku:

heroku create —stack cedar

Add the hostname as an environment variable. It is used by the rack middleware to build the callback urls:

heroku config:add HOST=<YOUR APP DOMAIN>

Add the superfeedr addon:

heroku addons:add superfeedr

Implementation

This implementation is minimalistic, feel free to look at the code closely.

Let’s add the files:

touch app.rb
touch config.ru
touch Gemfile
mkdir views/ && touch views/index.erb

In app.rb:

require 'sinatra'
require 'rack-superfeedr'
require 'cgi'

configure do
  # Application settings
  set :host, ENV['HOST']
  set :login, ENV['SUPERFEEDR_LOGIN']
  set :password, ENV['SUPERFEEDR_PASSWORD']
  
  # List all the feeds you want to subscribe to below.
  set :feeds, [
    'http://blog.superfeedr.com/atom.xml',
    'https://github.com/superfeedr',
    'http://feeds.feedburner.com/avc',
    'http://push-pub.appspot.com/feed'
  ]
  # The datastore... (volatile for this application)
  set :stories, {} 
end

# We use JSON for the data format
use(Rack::Superfeedr, { :host => settings.host, :login => settings.login, :password => settings.password, :format => 'json', :async => true }) do |superfeedr| 
  superfeedr.on_notification do |notification|
    notification['items'].each do |item|
      settings.stories[CGI::escape(item['id'])] = item # keeping the story
    end
  end
  
  # Subscribing to all the feeds we want. 
  # Subscriptions are stateful, so we could avoid resubscribing them everytime we boot the application, 
  # but we want to keep this application stateless for demo purposes
  settings.feeds.each do |url|
    superfeedr.subscribe(url)
  end
end

# Home page
get '/' do
  erb :index
end

# Redirects. Important for marking the stories as read!
get '/read/:id' do
  if params[:id] && entry = settings.stories[params[:id]] 
    settings.stories.delete(params[:id])
    if url = entry['permalinkUrl']
      redirect to(entry['permalinkUrl'])
    end
  else
    halt 404
  end
end

In config.ru:

require './app'
run Sinatra::Application

In Gemfile:

source 'http://rubygems.org'
gem 'sinatra'
gem 'rack-superfeedr'

In views/index.erb:

<!DOCTYPE html>
<html>
<head>
    <title>Start Page</title>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js" type="text/javascript"></script>
    <script src="http://twitter.github.com/bootstrap/1.4.0/bootstrap-dropdown.js"></script>
    <script src="http://twitter.github.com/bootstrap/1.4.0/bootstrap-alerts.js"></script>
    <link rel="stylesheet" href="http://twitter.github.com/bootstrap/1.4.0/bootstrap.min.css">
</head>
<body style="padding-top: 50px;">
    <section id="navigation">
        <div class="topbar-wrapper" style="z-index: 5;">
            <div class="topbar" data-dropdown="dropdown">
                <div class="topbar-inner">
                    <div class="container">
                        <h3><a href="/">Start Page</a></h3>
                        <ul class="nav">

                        </ul>
                    </div>
                </div><!-- /topbar-inner -->
            </div><!-- /topbar -->
        </div><!-- /topbar-wrapper -->
    </section>
    <div class="container">
        <% index = 0 %>
        <% settings.stories.each do |id, story| %>
        <% if (index % 3) == 0 %>
        <div class="row">
        <% end %>
            <div class="span5">  
                <h2><%= story["title"] %></h2>
                <p><%= (story["summary"].nil? || story["summary"] == ""  ? story["content"] : story["content"]).gsub(/<\/?[^>]*>/, "")[0..280] %></p>
                <p><a class="btn" href="/read/<%= CGI::escape(id) %>" target="_blank">Read &raquo;</a></p>
                              
            </div>
        <% if (index % 3) == 2 %>
        </div>
        <% end %>
        <% index += 1 %>
        <% end %>
    </div>
</body>
</html>

Let’s commit all files.

git add .
 git commit -m "implemented" -a

Deploying

That’s the simplest part:

First, let’s install the gems.

bundle install
 git add Gemfile.lock
 git commit -m "adding Gemfile.lock" -a

And push the code

git push heroku master

… And that’s it!

Point your browser to your Heroku application. It is likely that you’ll have to wait for the feeds you have added to have new content before you see it appear on your home page. The last step is to make this page your default start page in your favorite web browser.

Gotchas

Heroku will put this application to sleep if you only use a single dyno… and since we store the entries in memory for now, that means you’ll lose that data if the app goes idle. The solution is very simple: store the data, using another Heroku addon!

Of course, this is a very simple application and there is a ton of little things that one can do to improve it:

  • add the ability to dynamically subscribe to feeds
  • show meta data, like the number of bit.ly clicks on each of the stories
  • auto-refresh the page

The Superfeedr Heroku addon is still in alpha to this date, so if you want to run, this, please email us and we’ll get you in!

Liked this post? Read the archive or

On the same topic, check rack middleware for superfeedr and consuming rss feeds in rails application.

Previously, on the Superfeedr blog: Rack Middleware for Superfeedr.