- Understand the typical file structure for a Sinatra application
- Use the Rerun gem to help speed up development
In the last lesson, we saw a few of the key features of Sinatra, such as its ability to simplify routing logic and handle dynamic routes, as well as how to use Sinatra to send HTML and JSON data in response to HTTP requests.
In this lesson, we'll discuss how to use Sinatra and Active Record together, and how to organize our code in different files to help with our application's separation of concerns.
We'll also see some common gems used to speed up development when working with a Sinatra application.
This lesson has a good amount of starter code already set up for you. The majority of this code should be familiar — all of it was taken from a previous Active Record lesson on creating a many-to-many association. For the Active Record side of things, we have the models, migrations and seed data all set up. Run these commands to install the dependencies and set up the database:
$ bundle install
$ bundle exec rake db:migrate db:seed
Note: Running
rake db:migrate db:seed
on one line will run the migrations first, then the seed file. It's a nice way to save a few keystrokes!
We'll be working with the models and migrations more in the next lesson. For now, let's review the project's file structure and talk about how our code is organized.
So far, we've been setting up the file structure for our application in a way that nicely separates the concerns of our code. Each folder, and each file within that folder, has a clearly defined responsibility. Let's review:
app/models
: Our Active Record models. Responsible for code that accesses and updates data in our database using classes that inherit fromActiveRecord::Base
.config
: Code in this folder is responsible for our environment setup, like requiring files/gems, and establishing a connection to the database.db/migrate
: Our Active Record migrations. Responsible for creating and altering the structure of the database (making new tables, adding columns to existing tables, etc).db/seeds.rb
: Lets us easily add sample data to the database.spec
: Our RSpec tests.Gemfile
: Lists all the gems our application depends on.Rakefile
: Code for common tasks that we can easily run from the command line, likerake console
.
By organizing our code this way and clearly separating out the different parts of the application, it becomes much easier for us and other developers to know where to add new code when it's time to build onto or modify our app.
Note: This file structure also closely mirrors the structure of a typical Rails application, as you'll see in the next phase!
In the last lesson on Sinatra, we also saw some new code for writing a web
server. How does that code fit in with our current file structure? Let's break
it down a bit. Here's what that web server-specific code looks like in the
config.ru
file:
require 'sinatra'
class App < Sinatra::Base
get '/' do
'<h2>Hello <em>World</em>!</h2>'
end
end
run App
Where does this code belong? Well, we can leave some of it in the config.ru
file so that we'll be able to run our server with rackup config.ru
. But we can
also organize it a bit better so that we don't end up writing all of our
Sinatra-specific code in one file!
Let's make a new file for the App
class, called application_controller.rb
,
and move it to a new folder called app/controllers
(alongside the app/models
folder). Let's also update the class name to ApplicationController
so it
matches the name of the file:
# app/controllers/application_controller.rb
class ApplicationController < Sinatra::Base
get '/' do
'<h2>Hello <em>World</em>!</h2>'
end
end
Since our config/environment.rb
file does the work of require
-ing all our
gems as well as all the files in the app
folder, we can just require the
config/environment.rb
file in our config.ru
file and then run our
ApplicationController
:
# config.ru
require_relative "./config/environment"
run ApplicationController
Now as our application grows, it's easier to add code to the right place without having one file grow out of control!
Try running the server with this updated file structure and make sure your server still works:
$ rackup config.ru
You should still be able to visit http://localhost:9292/ and see the HTML response from the server.
Aside: You may be wondering: What's a controller? Why
ApplicationController
?The reason for using a
controllers
folder and a class named withController
at the end is because of a software pattern known as Model-View-Controller, or MVC. We'll cover MVC in more depth in the next phase, but essentially, it's a common pattern used by web app developers that helps with the separation of concerns of different parts of the application:
- Models: responsible for working with the database
- Views: responsible for what the user sees on the webpage (typically, views are some form of HTML template)
- Controllers: responsible for receiving a request and using the model to generate data needed for the view
For now, we won't go into too much depth on this subject — just know that MVC is a very common pattern in web development, and as long as you stick with this file structure, you'll be set up nicely to learn more about MVC down the road.
As developers, it's very helpful to be able to make small changes to our code and see what effect they have with as little friction as possible. Right now, any time we make a change to the code for our controller, we need to:
- Stop the server with
control + c
- Re-start the server with
rackup config.ru
We can simplify this process by using the Rerun Ruby gem, which watches for changes to our file system and reloads the program (similar to webpack development server's hot reload feature when working with React applications).
The Rerun gem is already included in the Gemfile, so we can try it out. To run our server in development mode using Rerun, run this command:
$ bundle exec rerun -b 'rackup config.ru'
Then make a request to http://localhost:9292/ in the browser. Just like before, we'll still see the "Hello World!" message. But now we can make changes to the application and simply refresh the page in the browser to see those changes, without having to manually restart the server!
Try updating the ApplicationController
and change the HTML being returned from
the GET /
route. Then refresh the page in the browser. You should see your new
code reflected in the updated response.
Note: It can take a few moments for
rerun
to fully restart the server. Keep an eye on the terminal output to see when the server has been successfully restarted.
To make it a bit easier to start the server, we can also make a custom Rake
task. Add this to your Rakefile
:
desc "Start the server"
task :server do
exec "rerun -b 'rackup config.ru'"
end
Now we can run the server with this Rake command:
$ bundle exec rake server
We've now built out the core structure of our Sinatra applications. We'll be introducing one or two more new gems in this section, but for the most part, this setup is what you'll use for the rest of this phase. The file structure you've seen here is also similar to what you'll use in the next phase with Rails, so by building it up gradually over the course of this phase, you've hopefully gained familiarity with many of these common conventions.