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