Previously, I talked about making first steps with Sinatra, how to prepare and configure everything to get the app running similarly to Rails. This time, I’m going to show you how to make the app as full-stack, so you could use it instead of Rails.

Config file

Every app sooner or later needs to store credentials somewhere. To do it so we can choose between at least two options.

Sinatra::ConfigFile

I’ve already mentioned about Sinatra: :Contrib , which offers some useful extensions. One of them is ConfigFile. The configuration is pretty easy:

require 'sinatra/base' require 'sinatra/config_file' class MyApp < Sinatra::Base register Sinatra::ConfigFile config_file 'path/to/config.yml' get '/' do @greeting = settings.greeting haml :index end # The rest of your modular application code goes here... end

We need to register the extension, define the config file and use one of the keys stored in the YAML file by invoking settings and the key.

Settingslogic

Another solution is a gem called settingslogic.

class Settings < Settingslogic source "#{MyApp.root}/config/application.yml" namespace MyApp.environment.to_s end

After adding it to Gemfile, you need to create Settings class, set a path to the config file and define a specific namespace, related to the app’s environment.

Which one is better? Well, Sinatra’s extension seems to be a bit faster to get done but, in my opinion, accessing nested keys is slightly uncomfortable, because you need to type brackets. In case of settingslogic, you shouldn’t forget about loading the class before autoloader, to have all keys available.



ActiveRecord

Yeah, that’s about time to introduce a database to the app. I’m pretty sure you know ActiveRecord, don’t you? There’s a gem called Sinatra ActiveRecord Extension. At the beginning let’s add some gems that we need. We’re going to use PostgreSQL by the way.

gem 'pg' gem 'rake' gem 'sinatra-activerecord', require: %w(sinatra/activerecord)

Now, we should register the extension.

class MyApp < Sinatra::Base register Sinatra::ActiveRecordExtension ... end



Of course we shouldn’t forget about the DB config file, at config/database.yml.

development: adapter: postgresql encoding: unicode database: my_app_development username: my_app password: host: localhost port: 5432

Because the gem offers some rake tasks as well, we have to create Rakefile and put into that the following lines:

require 'sinatra/activerecord/rake' require './my_app'

What’s now? You can simply create your first table. Type:

bundle exec rake db:create_migration NAME=create_users

And you will see first files in db directory. All other stuff related to ActiveRecord works the same as in a Rails app.



Views

As I said at the beginning, we’re going to build a full-stack app. I’ve decided to use HAML with Bootstrap, so let’s configure it. Firstly, add gem ‘haml’ to Gemfile and the following lines to your main application file.



class MyApp < Sinatra::Base set :haml, format: :html5, layout: :application set :views, File.join(root, 'app/views') end

The first line says we’re going to use HAML with HTML5 format. Layout key says that the main layout file is called application, so application.haml file should be located in the main view directory. When it comes to the directory there’s the second line which slightly changes the default behavior. We want to have our views directory the same as in Rails so we create it within app directory.

What’s next? Let’s use it in practice!

To choose a layout for the specific action type haml with a symbol that is the same as your file. In the example above I have the top template located in users directory, so additionally I need to use make it as a string and later convert to symbol.

Did we miss anything? Yeah, we did. Assets. We have to configure it manually too. Add a few gems to Gemfile:

gem 'sass' gem 'sprockets' gem 'uglifier'

I believe you recognize them very well. They need to be configured in the main file.

class MyApp < Sinatra::Base sprockets.append_path 'app/assets/stylesheets' sprockets.append_path 'app/assets/javascripts' sprockets.js_compressor = Uglifier.new(harmony: true) sprockets.css_compressor = :scss get '/app/assets/*' do env['PATH_INFO'].sub!('/app/assets', '') settings.sprockets.call(env) end end

What are we doing here? We need to include additional paths that will be used when serving assets, set the compressors for JS and CSS and create a route where the assets will be coming from.

It may look it’s done but it isn’t. We forgot to include assets in our main layout file.

# app/views/application.haml !!! %html %head %link{ href: "app/assets/app.css", rel: "stylesheet" } %script{ src: "app/assets/app.js" } %title My App %body

Yeah, now it’s working fine, but I’ve mentioned about Bootstrap, haven’t I? As always, let’s add gem ‘bootstrap’ to Gemfile.

As in Rails, we need to configure it in assets directory.

# app/assets/javascripts/app.js //= require jquery3 //= require popper //= require bootstrap-sprockets # app/assets/stylesheets/app.scss @import 'bootstrap';

There’s one change. Normally, you would use jquery-rails but unfortunately it doesn’t support Sinatra, so I simply created a file in app/assets/javascripts with JQuery. Of course, you can link to the latest version in application.haml as well.

Now you can polish your template a bit and check it out in the browser!

Sidekiq

The last thing we’d like to configure in the application is background processing. We’ll use Sidekiq for that. Let’s start with adding gem ‘sidekiq’ to Gemfile and create config/sidekiq.yml file containing, for example, the following configuration:

# config/sidekiq.yml --- :concurrency: 5 :queues: - default - rollbar



We also need to create an initializer so it might contain something like:

# config/initializers/sidekiq.rb Sidekiq.configure_client do |config| config.redis = { url: Settings.redis.url } end Sidekiq.configure_server do |config| config.redis = { url: Settings.redis.url } end

Okay, great! It’s also done. If you consider setting up the Sidekiq UI you can easily configure it in config.ru file.

# config.ru require './my_app' require 'sidekiq/web' if App.production? Sidekiq::Web.use Rack::Auth::Basic do |username, password| [username, password] == [Settings.sidekiq.username, Settings.sidekiq.password] end end run MyApp run Rack::URLMap.new('/' => MyApp, '/sidekiq' => Sidekiq::Web)

What’s going on above? We enable the UI at /sidekiq and add authorization using username and password stored in the settings file.

Helpers

It’s also possible to add some helpers. Let’s say I want to protect my endpoint returning top 5 users.

class MyApp get '/top' do protected! @users = User.top5 haml :'users/top' end end

How should the helper look? You may use helpers block.

# app/helpers/authentication_helpers.rb class MyApp helpers do def protected! return if MyApp.development? || authorized? headers["WWW-Authenticate"] = "Basic realm=\"Restricted Area\"" halt 401, "Not authorized

" end def authorized? @auth ||= Rack::Auth::Basic::Request.new(request.env) @auth.provided? && @auth.basic? && @auth.credentials && @auth.credentials == [Settings.authentication.username, Settings.authentication.password] end end end

Deployment to Heroku

We want our application working on Heroku but before doing it we need to create Procfile, that runs a web and worker dynos.

# Procfile web: bundle exec puma -e $RACK_ENV -t 1:5 worker: bundle exec sidekiq -e $RACK_ENV -r ./app.rb

Of course, you should enable the dynos, Postgres and Redis add-ons first.



Console

In some cases, e.g. during the development process, it may be useful to run a console. As you can expect, rails c won’t work here but you can type irb -r ./my_app.rb to run IRB with all classes loaded. If you’d like to run it in production you need to type heroku run bash first, but keep in mind that all actions in production environment can be unsafe!



Summary

Going to the end, now you should be able to build your first full-stack application in Sinatra. It’s not that hard, all you need to do is configuring some stuff on your own, without the magic that happens in Rails. Nevertheless, you can get a lightweight Ruby app so it’s worth doing it. I highly recommend you visit Sinatra documentation to explore more cool things!