First Steps with Sinatra as a Rails Developer - Part 1

Some time ago, I had a chance to build a very small app handling HTTP requests coming from Slack.
I decided to choose a lightweight framework, so I skipped Rails in favour of Sinatra. In this blog post, I would like to help you out with the first steps toward using something different than Rails, but from the perspective of a Rails developer.
Why? Well, a lot of Ruby developers might be called Rails developers because the only framework they have ever used is Rails. As you can see here, there are many other web frameworks for Ruby, and I’m pretty sure that most of them sound strange to you. Let me introduce Sinatra!
What’s Sinatra?
According to the website,
Sinatra is a DSL for quickly creating web applications in Ruby with minimal effort.
In other words, it’s a microframework for Ruby, which, in the very basic version, offers us some settings, routing, and a few request helpers.
How to start?
# Install the gem
gem install sinatra
# my_app.rb
require 'sinatra'
get '/' do
'Hello world!'
end
ruby my_app.rb
The snippet above is the most basic example of an endpoint created in Sinatra, available at http://localhost:4567.
Classic vs. modular apps
In Sinatra, you will basically encounter two types of applications. Classic applications are often a single file run from the command line, and with no more than one app per process. When it comes to modular applications, they can be used for building a more complex app or a library. Each of them has different default settings.
Setting |
Classic |
Modular |
Modular |
app_file |
file loading sinatra |
file subclassing Sinatra::Base |
file subclassing Sinatra::Application |
run |
$0 == app_file |
false |
false |
logging |
true |
false |
true |
method_override |
true |
false |
true |
inline_templates |
true |
false |
true |
static |
true |
File.exist?(public_folder) |
true |
- app_file and run indicate how we load the main app file. With the classic style, we use the built-in web server by simply running a Ruby script file.
- logging writes requests as single lines to STDERR.
- method_override is especially useful in form submissions to make a hack with POST requests that may look like PUT or DELETE methods.
- inline_templates: if you want to include a view template in the same file where your endpoint is, it needs to be set as true.
- static says whether we provide static files, usually located in the public directory.
Other settings are available here. In this blog post, we’ll follow the second style, inheriting from Sinatra::Base.
# my_app.rb
require 'sinatra/base'
class MyApp < Sinatra::Base
get '/' do
'Hello world!'
end
end
Don’t forget about config.ru
When should we add a config.ru file to the app? Do it if you want to:
- deploy with a different Rack handler (Passenger, Unicorn, Heroku etc.);
- use more than one subclass of Sinatra::Base;
- use Sinatra only for middleware, and not as an endpoint.
Then the file might look as follows:
require './my_app'
run MyApp
File structure and autoloading
If we want to apply some kind of a file structure or even make our app similar to a Rails app, we should be aware of one important thing: autoloading. In Rails, it happens out of the box, but here, we need to do it on our own. One of the ways might be creating an initializer at config/initializers called autoloader.rb that would iterate over specified files and require them.
# config/initializers/autoloader.rb
paths = %w[config/initializers/*.rb app/**/*.rb].map(&:freeze).freeze
paths.each do |path|
Dir[File.join(MyApp.root, path)].each do |file|
next if file.include?('initializers/autoloader') # skip me
require file
end
end
And, of course, we need to require the file manually at the end of my_app.rb.
class MyApp < Sinatra::Base
set :root, File.dirname(__FILE__)
…
require File.join(root, '/config/initializers/autoloader.rb')
end
Be careful, though! If you use the inheritance, for instance, in a service, you should either use require_relative with the name of the parent class in the service or specify it directly in the autoloader. Otherwise, you’ll get an error.
Reloading
Rails offers us a lot of magic and makes us a bit lazy. In Sinatra, we need to think as if we were building the app with LEGO bricks. In this case, another brick is reloading. By default, if you introduce a change in a file, you won’t see the change until you reload your app manually. How to avoid this? There are two ways:
Sinatra::Reloader
In its repository, Sinatra contains a collection of semi-officially supported extensions called Contrib. You will find there the reloader, which reloads Ruby files upon code changes.
require 'sinatra/base'
require 'sinatra/reloader'
class MyApp < Sinatra::Base
configure :development do
register Sinatra::Reloader
end
# Your modular application code goes here...
end
So, in development environment, we have to register the extension, earlier require it and voilà! If you don’t like it, there’s an another option (see below).
Shotgun
Shotgun is an alternative to the complex reloading logic provided by web frameworks or in environments that don't support application reloading. All you need to do is:
# Gemfile
group :development do
gem 'shotgun'
end
# Run the command
shotgun
# == Shotgun/Puma on http://127.0.0.1:9393/
# …
Okay, but which one should you use? In my opinion, Sinatra::Reloader works a bit faster, but it doesn’t load new files, only changes in existing ones, so… it’s up to you. ;)
Gems
What about gems in Sinatra apps? Basically, most gems used in Rails apps are also supported in Sinatra, so no worries. However, there’s one thing that you should be aware of when adding more and more gems. If you want to use them within the app, you should require them in the main file. In my case, after adding a few gems to my app, the beginning of the file started looking like this:
require 'app_konfig'
require 'haml'
require 'httparty'
require 'pry'
require 'puma'
require 'sidekiq'
require 'sinatra/base'
require 'sinatra/reloader'
It could look even worse, depending on how many gems you want to use. To solve this problem, there's a perfect solution provided by Bundler.
# my_app.rb
require 'bundler'
Bundler.require(:default, ENV.fetch('RACK_ENV', 'development'))
These two lines do the job and allow us to forget about requiring every gem we need explicitly.
Finally, you might have noticed that for Sinatra, I needed to require base and reloader. How to do it with the solution above? Specify them in your Gemfile:
gem 'sinatra', require: %w(sinatra/base sinatra/reloader)
gem 'sinatra-contrib'
Okay, at the moment, we’ve covered the first few steps with Sinatra. In the second part of the blog post, I’ll present how to build a full-stack Sinatra app that could be used instead of Rails. Stay tuned!
Photo by Max Nelson on Unsplash