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:
Before we begin writing our application, let's write the Hello World application.
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:
To understand how to write a Sinatra application, we will take a small project and discuss every part of the program in detail.
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:
The modules that we build are as follows:
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.
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:
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 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.
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.
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.
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:
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:
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.
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
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 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!
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.
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.
In this article, you learned how to build a Hello World program using Sinatra.
Further resources on this subject: