11 min read

In this article by Manoj Kumar, author of the book Learning Sinatra, we will write an application. Make sure that you have Ruby installed. We will get a basic skeleton app up and running and see how to structure the application.

(For more resources related to this topic, see here.)

In this article, we will discuss the following topics:

  • A project that will be used to understand Sinatra
  • Bundler gem
  • File structure of the application
  • Responsibilities of each file

Before we begin writing our application, let’s write the Hello World application.

Getting started

The Hello World program is as follows:

1require 'sinatra'
2
3 get '/' do
4 return 'Hello World!'
5 end

The following is how the code works:

ruby helloworld.rb

Executing this from the command line will run the application and the server will listen to the 4567 port. If we point our browser to http://localhost:4567/, the output will be as shown in the following screenshot:

The application

To understand how to write a Sinatra application, we will take a small project and discuss every part of the program in detail.

The idea

We will make a ToDo app and use Sinatra along with a lot of other libraries. The features of the app will be as follows:

  • Each user can have multiple to-do lists
  • Each to-do list will have multiple items
  • To-do lists can be private, public, or shared with a group
  • Items in each to-do list can be assigned to a user or group

The modules that we build are as follows:

  • Users: This will manage the users and groups
  • List: This will manage the to-do lists
  • Items: This will manage the items for all the to-do lists

Before we start writing the code, let’s see what the file structure will be like, understand why each one of them is required, and learn about some new files.

The file structure

It is always better to keep certain files in certain folders for better readability. We could dump all the files in the home folder; however, that would make it difficult for us to manage the code:

The app.rb file

This file is the base file that loads all the other files (such as, models, libs, and so on) and starts the application. We can configure various settings of Sinatra here according to the various deployment environments.

The config.ru file

The config.ru file is generally used when we need to deploy our application with different application servers, such as Passenger, Unicorn, or Heroku. It is also easy to maintain the different deployment environment using config.ru.

Gemfile

This is one of the interesting stuff that we can do with Ruby applications. As we know, we can use a variety of gems for different purposes. The gems are just pieces of code and are constantly updated. Therefore, sometimes, we need to use specific versions of gems to maintain the stability of our application.

We list all the gems that we are going to use for our application with their version. Before we discuss how to use this Gemfile, we will talk about gem bundler.

Bundler

The gem bundler manages the installation of all the gems and their dependencies. Of course, we would need to install the gem bundler manually:

gem install bundler

This will install the latest stable version of bundler gem. Once we are done with this, we need to create a new file with the name Gemfile (yes, with a capital G) and add the gems that we will use.

It is not necessary to add all the gems to Gemfile before starting to write the application. We can add and remove gems as we require; however, after every change, we need to run the following:

bundle install

This will make sure that all the required gems and their dependencies are installed. It will also create a ‘Gemfile.lock‘ file. Make sure that we do not edit this file. It contains all the gems and their dependencies information.

Therefore, we now know why we should use Gemfile.

This is the lib/routes.rb path for folder containing the routes file.

What is a route?

A route is the URL path for which the application serves a web page when requested. For example, when we type http://www.example.com/, the URL path is / and when we type http://www.example.com/something/, /something/ is the URL path.

Now, we need to explicitly define all the routes for which we will be serving requests so that our application knows what to return. It is not important to have this file in the lib folder or to even have it at all. We can also write the routes in the app.rb file.

Consider the following examples:

get '/' do
# code
end

post '/something' do
# code
end

Both of the preceding routes are valid. The get and post method are the HTTP methods. The first code block will be executed when a GET request is made on / and the second one will be executed when a POST request is made on /something.

The only reason we are writing the routes in a separate file is to maintain clean code. The responsibility of each file will be clearly understood in the following:

  • models/: This folder contains all the files that define model of the application. When we write the models for our application, we will save them in this folder.
  • public/: This folder contains all our CSS, JavaScript, and image files.
  • views/: This folder will contain all the files that define the views, such as HTML, HAML, and ERB files.

The code

Now, we know what we want to build. You also have a rough idea about what our file structure would be.

When we run the application, the rackup file that we load will be config.ru. This file tells the server what environment to use and which file is the main application to load.

Before running the server, we need to write a minimum code. It includes writing three files, as follows:

  • app.rb
  • config.ru
  • Gemfile

We can, of course, write these files in any order we want; however, we need to make sure that all three files have sufficient code for the application to work.

Let’s start with the app.rb file.

The app.rb file

This is the file that config.ru loads when the application is executed. This file, in turn, loads all the other files that help it to understand the available routes and the underlying model:

  1 require 'sinatra'
  2
  3 class Todo < Sinatra::Base
  4  set :environment, ENV['RACK_ENV']
  5
  6  configure do
  7  end
  8
  9  Dir[File.join(File.dirname(__FILE__),'models','*.rb')].each { |model| require model }
  10  Dir[File.join(File.dirname(__FILE__),'lib','*.rb')].each { |lib| load lib }
  11
  12 end

What does this code do?

Let’s see what this code does in the following:

     1 require 'sinatra'
//This loads the sinatra gem into memory.
     3 class Todo < Sinatra::Base
     4  set :environment, ENV['RACK_ENV']
     5
     6  configure do
     7  end
     8
     9  Dir[File.join(File.dirname(__FILE__),'models','*.rb')].each { |model| require model }
     10  Dir[File.join(File.dirname(__FILE__),'lib','*.rb')].each { |lib| load lib }
     11
     12 end

This defines our main application’s class. This skeleton is enough to start the basic application. We inherit the Base class of the Sinatra module.

Before starting the application, we may want to change some basic configuration settings such as logging, error display, user sessions, and so on. We handle all these configurations through the configure blocks. Also, we might need different configurations for different environments. For example, in development mode, we might want to see all the errors; however, in production we don’t want the end user to see the error dump. Therefore, we can define the configurations for different environments.

The first step would be to set the application environment to the concerned one, as follows:

     4  set :environment, ENV['RACK_ENV']

We will later see that we can have multiple configure blocks for multiple environments. This line reads the system environment RACK_ENV variable and sets the same environment for the application. When we discuss config.ru, we will see how to set RACK_ENV in the first place:

     6  configure do
     7  end

The following is how we define a configure block. Note that here we have not informed the application that to which environment do these configurations need to be applied. In such cases, this becomes the generic configuration for all the environments and this is generally the last configuration block. All the environment-specific configurations should be written before this block in order to avoid code overriding:

     9  Dir[File.join(File.dirname(__FILE__),'models','*.rb')].each { |model| require model }

If we see the file structure discussed earlier, we can see that models/ is a directory that contains the model files. We need to import all these files in the application. We have kept all our model files in the models/ folder:

Dir[File.join(File.dirname(__FILE__),'models','*.rb')]

This would return an array of files having the .rb extension in the models folder. Doing this, avoids writing one require line for each file and modifying this file again:

   10  Dir[File.join(File.dirname(__FILE__),'lib','*.rb')].each { |lib| load lib }

Similarly, we will import all the files in the lib/ folder.

Therefore, in short, the app.rb configures our application according to the deployment environment and imports the model files and the other library files before starting the application.

Now, let’s proceed to write our next file.

The config.ru file

The config.ru is the rackup file of the application. This loads all the gems and app.rb. We generally pass this file as a parameter to the server, as follows:

1 require 'sinatra'
2 require 'bundler/setup'
3 Bundler.require
4
5 ENV["RACK_ENV"] = "development"
6
7 require File.join(File.dirname(__FILE__), 'app.rb')
8
9 Todo .start!

W Working of the code

Let’s go through each of the lines, as follows:

1 require 'sinatra'
2 require 'bundler/setup'

The first two lines import the gems. This is exactly what we do in other languages. The gem ‘sinatra’ command will include all the Sinatra classes and help in listening to requests, while the bundler gem will manage all the other gems. As we have discussed earlier, we will always use bundler to manage our gems.

3 Bundler.require

This line of the code will check Gemfile and make sure that all the gems available match the version and all the dependencies are met. This does not import all the gems as all gems may not be needed in the memory at all times:

5 ENV["RACK_ENV"] = "development"

This code will set the system environment RACK_ENV variable to development. This will help the server know which configurations does it need to use. We will later see how to manage a single configuration file with different settings for different environments and use one particular set of configurations for the given environment.

If we use version control for our application, config.ru is not version controlled. It has to be customized on whether our environment is development, staging, testing, or production. We may version control a sample config.ru. We will discuss this when we talk about deploying our application.

Next, we will require the main application file, as follows:

7 require File.join(File.dirname(__FILE__), 'app.rb')

We see here that we have used the File class to include app.rb:

File.dirname(__FILE__)

It is a convention to keep config.ru and app.rb in the same folder. It is good practice to give the complete file path whenever we require a file in order to avoid breaking the code. Therefore, this part of the code will return the path of the folder containing config.ru.

Now, we know that our main application file is in the same folder as config.ru, therefore, we do the following:

File.join(File.dirname(__FILE__), 'app.rb')

This would return the complete file path of app.rb and the line 7 will load the main application file in the memory. Now, all we need to do is execute app.rb to start the application, as follows:

9 Todo .start!

We see that the start! method is not defined by us in the Todo class in app.rb. This is inherited from the Sinatra::Base class. It starts the application and listens to incoming requests.

In short, config.ru checks the availability of all the gems and their dependencies, sets the environment variables, and starts the application.

The easiest file to write is Gemfile. It has no complex code and logic. It just contains a list of gems and their version details.

Gemfile

In Gemfile, we need to specify the source from where the gems will be downloaded and the list of the gems. Therefore, let’s write a Gemfile with the following lines:

     1 source 'https://rubygems.org'
     2 gem 'bundler', '1.6.0'
     3 gem 'sinatra', '1.4.4'

The first line specifies the source. The https://rubygems.org website is a trusted place to download gems. It has a large collection of gems hosted. We can view this page, search for gems that we want to use, read the documentation, and select the exact version for our application.

Generally, the latest stable version of bundler is used. Therefore, we search the site for bundler and find out its version. We do the same for the Sinatra gem.

Summary

In this article, you learned how to build a Hello World program using Sinatra.

Resources for Article:


Further resources on this subject:


LEAVE A REPLY

Please enter your comment!
Please enter your name here