Create a Gem of an Extension in Sinatra

RubySource contributor Darren Jones has written a book on Sinatra as part of SitePoint’s latest Jump Start series. With these “novella-sized” books, devs pressed for time can get up to speed on a new language or framework in just one weekend.

In this excerpt, Darren explains how to create an admin extension in Sinatra. Take it away, Darren.

Creating an Admin Extension

In Chapter 4, we used sessions to create route handlers that allowed users to log in and out of the application. We’re now going to create some useful helper methods that protect pages and check whether a user is logged in or not. We’re also going to register helper methods another way — as a Sinatra extension. Extensions go in separate files and include helper methods, settings, and route handlers. To use the extension, you require the file in your application. This makes them reusable, and they can also be packaged as a gem for distribution. To produce the extension, create a new folder in the root directory called sinatra. Inside this folder, create a new file called auth.rb. The full code for this extension is shown:

require 'sinatra/base'
require 'sinatra/flash'

module Sinatra
  module Auth
    module Helpers
      def authorized?
        session[:admin]
      end

      def protected!
        halt 401,slim(:unauthorized) unless authorized?
      end
    end

    def self.registered(app)
      app.helpers Helpers

      app.enable :sessions
      app.set :username => 'frank', :password => 'sinatra'

      app.get '/login' do
        slim :login
      end

      app.post '/login' do
        if params[:username] == settings.username && params[:password] == settings.password
          session[:admin] = true
          flash[:notice] = "You are now logged in as #{settings.username}"
          redirect to('/')
        else
          flash[:notice] = "The username or password you entered are incorrect"
          redirect to('/login')
        end
      end

      app.get '/logout' do
        session[:admin] = nil
        flash[:notice] = "You have now logged out"
        redirect to('/')
      end
    end
  end

  register Auth

end

At the top of the file, we require 'sinatra/base'. Every Sinatra extension has to require this file—it is the core of Sinatra minus the code needed to be an actual application.

We’re also requiring Sinatra::Flash as the extension will use flash for displaying messages after the user logs in and out.

Our extension is then created as a module nested inside a Sinatra module. This is the standard structure for all Sinatra extensions.

We place the helper methods that our extension will use at the start of the module. These are created inside their own module called Helpers (you can call it whatever you like, but Helpers seems to make sense).

Inside the Helpers module, we’ve added two helper methods. The first is authorized?, which checks to see if a user has logged in by checking if the value of session[:admin] is true. This is a good method to use in route handlers and views.

The second method is protected!. It specifies that a route handler can only be accessed by a user who is logged in (notice that it utilizes the authorized? helper method to check this).

This next one uses Sinatra’s halt method, which immediately stops a request and returns a specified HTTP code (401 in this case). It also shows a view called unauthorized, which we’ll produce shortly and save in our views directory.

After the helpers, we’ll devise a special method called self.registered(app). This contains all the settings for the extension and all the route handlers. It also specifies the name of the helpers module that we just created. All the methods contained inside this method need to be methods of the app object, which is the argument of the self.registered method, and is, in fact, the application using the extension.

The next line says to use the module called Helpers for the helpers.

After this, we enable sessions and create some settings. These settings can be overridden in main.rb, which means that you can change all the settings used by the extension without having to touch the extension file. They are just default settings.

Then there are the route handlers. These are much the same as those that we created in the last chapter, although we’re now using Sinatra::Flash to add some nice messages when the user logs in and out.

Finally, at the end of the file, we register the extension.

Once you’ve saved the file, add the following line to the list of requires at the top of main.rb:

require './sinatra/auth'

Finally, as mentioned above, we need an unauthorized view:

h1 Unauthorized
p You need to be logged in to view this page.

Restart the server, and you should be able to log in and out using our new extension. To make things easier, let’s add a link so that users can do this in a footer. We want this to be on every page, so let’s add the following code to the bottom of layout.slim:

footer
  - if authorized?
    a href="/logout" log out
  - else
    a href="/login" log in

This uses the authorized? helper to check if the user is logged in or not, and then presents the relevant link (to either log in or out).

Our last task is to utilize the protected! method to require users to log in to certain pages. For example, the page for creating a new song should only be available to a user who is logged in, so it should have the protected! method placed at the start of the route handler. Open up song.rb and change it so that it looks like
the following:

get '/songs/new' do
protected!

I’d strongly advise you to add the protected! method to the create, edit, update, and
delete route handlers too!

The point of making the Auth extension is so that it can be reused in other applications, but it’s highly unlikely that other applications will want to use the same username and password (not to mention the security risk). Changing these is
straightforward, though; you don’t even have to edit the auth.rb file; you can just set them from within main.rb, like so:

set :username, 'daz'
set :password, 'secret'

Settings in the main application file will always supersede the default settings in the extension file, making it easy to customize an extension’s settings without having to go poking around in the extension’s code.

Thanks for that, Daz. There you have it, just some of the expertise you can expect when you purchase Darren’s new book, Jump Start Sinatra.

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • Richard

    Looks very helpful! What runtime envirnment do I use to run: ruby/sinatra?

    • http://daz4126.com/ Darren Jones

      Hi Richard,

      You just need a machine that has Ruby and the Sinatra Gem installed.

      Hope that helps,

      DAZ