Developing a Sinatra app with Ruby can be challenging, especially because there are many conventions and rules you have to follow to create a well-structured app. That part isn’t really bad news though, in fact, it makes your job easier because you don’t have to make those decisions that are already made for you (because we all know how hard it is to make decisions).
The Example (Favorite Places)
The app I build, called Favorite Places is a simple CRUD application that lets you sign up/sign in and create your very own favorite places (name, city, country). You can create, read, update and delete your own places and view entries by other people.
I will be explaining roughly how I set up my app, the tools I used, and the concepts around it. If you want to try out the app:
- Clone the repo Favorite Places
cdinto the folder
bundle install(to install all the gems) and
rake db:migrate(to set up the database)
shotgun(to create a server) and open the provided link in your browser
It would be possible to create such an application all in one file, with hundreds, maybe thousands of lines of code in it - but that would get problematic quickly. You should separate your application so that writing, reading and debugging becomes easier and a lot more pleasant - not just for you but possible contributors as well.
One popular separation of concerns structure is Model-View-Controller or MVC:
- Models: Models are the “brains” of your application, this is the place to manipulate your data.
- Views: Views provide the user interface of your application, everything the user can see and interact with is stored in your views.
- Controllers: Controllers are the layer between the views and the models, they connect the two and make them talk to each other.
Read more about the Model-View-Controller Structure: Understanding Model-View-Controller.
There are certain naming conventions for an MVC folder structure that you should follow:
The naming conventions will make more sense after looking at the File/Folder Structure.
When you are staring with a blank file, it’s always nice to have some kind of template to lean on, so you don’t lose your mind completely. The folder structure I am outlining here is pretty standard and not necessarily specific to the application I built; therefore, it can be used for many different Sinatra projects.
This directory holds the main part of our application, it neatly contains our models, views, and controllers.
This folder contains our models, each model file represents a component of your application - in this case User, Place, and Country. The application controller is what ties the other controllers together and is generally required for an application such as this one. It contains a
configure block which tells the controller where to find the views and the public directory and usually
helpers, which is where global helper methods go.
This directory holds different views, which the end user is going to see. The views are again grouped into folders for each component.
layout (provides a layout for all your erb files) in the main directory are not specific to any component.
The views files are in the
erb file format (embedded Ruby) which allows you to write plane old html interspersed with Ruby elements.
This contains your
environment.rb file, connecting all of the files in our application to the appropriate gems and each other.
This directory holds your database and database migrations. To create a new migration simply run
rake db:create_migration NAME=create_users with the name to match your migration - and rake will create a migration for you with the appropriate time stamp.
If you were to run test for your application, this is where they would be stored.
You need a
config.ru when building Rack based applications. It loads your application environment, code, libraries, and specifies which controllers to load.
This holds a list of all the gems that are required for the application. Running
bundle install in your command line will install all the listed gems and dependencies. To create a new
Gemfile simply type
bundle init in your terminal (make sure your’re in the root of your project).
Rakefile you can specify your own rake tasks. To see all of the rake tasks that are available to you simply run
rake -T. Learn more about creating Rake tasks: Using Rake to Automate Tasks.
You may want to add a
README.md to your directory to help other programmers understand your application, figure out how to use it and learn how they may contribute. The
LICENSE.md file contains your license.
CRUD is an acronym for Create, Read, Update, and Delete, these are the four basic functions of persistent storage.
Create allows a user to create or add new entries; this adds a new record to the database (executing an INSERT statement). In your controller this is the action that renders the /places/new form view which when submitted creates a new instance of your model.
Read is to read, retrieve or view existing entries; it fetches specific records from the database and displays them on the page. It shows your user either a specific instance of a class (/places/:id) or all of them (/places or /users/:username).
Update allows for updating or editing existing entries which modifies a record in your database (it performs an UPDATE statement). For this to work you need a controller action that renders an update form view for a specific entry (/places/:id/edit) and another that receives the information of the posted form.
Delete permits a user to delete or remove an existing entry; it deletes a record (a specific row) in your database. Destroying as opposed to the other actions is generally only available through a delete button (in our case on /users/:username), rather than a url.
Sinatra is a Domain Specific Language for writing web applications. It is dependent on Rack (which creates the interface behind Sinatra) and is an alternative to other frameworks such as Ruby on Rails but much more light-weight. In a nutshell, Sinatra provides us with pre-written methods that we can use to turn, for example, our CLI application into a web application.
Databases can get pretty big and complicated, that’s why we need help talking to them. Building our own methods to talk to our database is tedious and not necessary anymore - This is where ORM comes in.
ORM or Object Relational Mapping is the part that sits between your database and your application. The convention is to map (or equate) ruby classes with database tables and the instances of those classes with the rows of the table. ActiveRecord is a Ruby library that does exactly that - it connects classes to relational SQL Databases.
So essentially ActiveRecord is an ORM that helps you deal with databases or as rubyonrails.org explains it: “ActiveRecord is the M in MVC - the model - which is the layer of the system responsible for representing business data and logic.”
If you do decide to make your project public, I recommend using version control so other people can understand the changes you’ve made and you always have a way of going back to previous code. Even if you don’t want anyone to see your code, it’s a great option for understanding your code after weeks, months or even years.
One specific topic that held me back when I was first creating this app were session messages; more specifically how to delete a certain session message after a page refresh. The problem I had was that upon refreshing a page and after a session message had already been loaded, this message would load again and again with each refresh.
This is because our message, after a first render, is stored in our session cookie and will stay there until the session is terminated.
The solution ended up being a lot easier that I thought. Instead of using
@message = session[:message] you simply write
@message = session.delete(:message). This will delete the session message with every page refresh.
- Check out transient state at the bottom of this Sessions in Sinatra article