Songs By Sinatra
In the book, we build a sample application called Songs by Sinatra. This is a site dedicated to the songs of the great Frank Sinatra. It allows a user who is logged in to add songs by Ol’ Blue Eyes, including the title, date, length and lyrics. It also allows visitors to the site to ‘like’ these songs. The live site can be seen here.Creating the File Structure
The first job is to create the file structure. Since we are using an MVC structure, it makes sense to have ‘models’, ‘views’ and ‘controllers’ folders. I also decided to create a ‘helpers’ folder and ‘lib’ folder for any extensions and middleware. In the book, we created a small piece of middleware to handle assets (such as CoffeeScript and Sass files), so we have an ‘assets’ folder, as well as the standard public folder for all the publicly available resources, such as images. Here’s a diagram of my folder structure:One Controller to Rule them All
I also suggest in the book to write a global controller called ApplicationController. This should use all the views, layouts and register any extensions used by the whole application.$:.unshift(File.expand_path('../../lib', __FILE__))
require 'sinatra/base'
require 'slim'
require 'sass'
require 'coffee-script'
require 'v8'
require 'sinatra/auth'
require 'sinatra/contact'
require 'sinatra/flash'
require 'asset-handler'
class ApplicationController < Sinatra::Base
helpers ApplicationHelpers
set :views, File.expand_path('../../views', __FILE__)
enable :sessions, :method_override
register Sinatra::Auth
register Sinatra::Contact
register Sinatra::Flash
use AssetHandler
not_found{ slim :not_found }
end
This requires all the necessary gems that are used and then creates an ApplcationController
class. This will be the base class for all controllers. It registers the ApplicationHelpers
, which will be where all application-wide helpers go.
The views folder needs to be set here, as it is not located in the same directory as our application_controller.rb file which is what Sinatra expects. This is easy to change, though, using the set
command.
We also enable sessions
and method_override
here. Sessions are needed to use sinatra-flash and will also be required for most applications. The method_override
setting is used to allow browsers to support HTTP methods such as PUT, PATCH and DELETE using a POST method and hidden input field in a form.
Extensions
In the book, I went through building anAuth
extension module. I also explained how to write some helper methods for sending a contact email. I extracted these and the contact routes into their own extension so that it could be set separately
The settings for these can be set in the ApplicationController
, so the code in the extensions doesn’t need to be edited at all.
Other Controllers
The other controllers now inherit from theApplicationController
class. There are two controllers in this application – the WebsiteController
, responsible for the main part of the site and the SongController
, responsible for all the CRUD operations performed on the Song
model.
class WebsiteController < ApplicationController
helpers WebsiteHelpers
get '/' do
slim :home
end
get '/about' do
@title = "All About This Website"
slim :about
end
end
class SongController < ApplicationController
helpers SongHelpers
get '/' do
find_songs
slim :songs
end
get '/new' do
protected!
find_song
slim :new_song
end
get '/:id' do
find_song
slim :show_song
end
post '/songs' do
protected!
create_song
flash[:notice] = "Song successfully added"
redirect to("/#{@song.id}")
end
get '/:id/edit' do
protected!
find_song
slim :edit_song
end
put '/:id' do
protected!
update_song
flash[:notice] = "Song successfully updated"
redirect to("/#{@song.id}")
end
delete '/:id' do
protected!
find_song.destroy
flash[:notice] = "Song deleted"
redirect to('/')
end
post '/:id/like' do
find_song
@song.likes = @song.likes.next
@song.save
redirect to("/#{@song.id}") unless request.xhr?
slim :like, :layout => false
end
end
Models
There is only one model in this case – theSong
model. In the model directory, there is just one file song.rb that creates the Song
class and sets up all the DataMapper properties:
require 'dm-core'
require 'dm-migrations'
class Song
include DataMapper::Resource
property :id, Serial
property :title, String
property :lyrics, Text
property :length, Integer
property :released_on, Date
property :likes, Integer, :default => 0
def released_on=date
super Date.strptime(date, '%m/%d/%Y')
end
DataMapper.finalize
end
Note that I’m using DataMapper for this model, but could easily use another ORM. In fact, I could even use a different ORM in a another model.
Helpers
Each controller has its own helper file, so there is application-helpers.rb, website-helpers.rb, and song-helpers.rb. These are created as a module and then each controller has to explicilty register the associated helpers module. Here is the application helpers file:module ApplicationHelpers
def css(*stylesheets)
stylesheets.map do |stylesheet|
"<link href="/#{stylesheet}.css" media="screen, projection" rel="stylesheet" />"
end.join
end
def current?(path='/')
request.path_info==path ? "current": nil
end
end
It is registered in application_controller.rb with the following line:
helpers ApplicationHelpers
The application helpers are where all the global helper methods go. The two above are used in the layout to make it easier to include links to CSS files and another helper to add a class of ‘current’ to a link if it is linking to the current page.
Rackup
The config.ru is where all the configuration is done:require 'sinatra/base'
Dir.glob('./{models,helpers,controllers}/*.rb').each { |file| require file }
SongController.configure :development do
DataMapper.setup(:default, "sqlite3://#{Dir.pwd}/development.db")
end
SongController.configure :production do
DataMapper.setup(:default, ENV['DATABASE_URL'])
end
map('/songs') { run SongController }
map('/') { run WebsiteController }
First of all we require Sinatra::Base
(as opposed to Sinatra
, since we are using a modular-style structure). Then we require all the files kept in the models, helpers and controllers folders.
After this we do a bit of configuration for the SongController
class to set up the database for the production and development environments. This could have been placed in the song_controller.rb file, but the config.ru seemed the best place to put the configuration.
Last of all we use the handy map
methods that Rack gives us to create a namespace for each controller.
That’s All Folks
Sinatra’s flexibiltiy and modular style made it very easy to refactor the application into a framework-style structure. I think it hits a nice balance between code organization and still keeping things relatively simple. You still have to do a few more things by hand – like setting up views and database connections by hand. Going forward I think I’d like to look at perhaps adding a few more extensions, using a better Asset Handler and organizing the database and ORM a bit better. I’ve put all the code on GitHub and called it ‘Jump Start’. The name seems doubly appropriate given how it came about and because it helps you ‘jump start’ your Sinatra projects. Would you use Jump Start? Can you think of any ways to improve it? Have you created your own bespoke framework using Sinatra? As usual, let us know in the comments below.Frequently Asked Questions (FAQs) about Building a Sinatra MVC Framework
What is Sinatra MVC and why is it important?
Sinatra MVC (Model-View-Controller) is a design pattern used in software development. It separates an application into three interconnected parts: the Model, the View, and the Controller. This separation allows developers to manage complex applications and systems by breaking them down into smaller, more manageable pieces. It’s important because it promotes organized programming and enhances the scalability and maintainability of applications.
How does Sinatra MVC differ from other MVC frameworks?
Sinatra MVC is a lightweight and flexible framework. Unlike other MVC frameworks like Rails, Sinatra doesn’t come with many built-in tools or functions. This means developers have more freedom to customize their applications, but it also means they may need to write more code to achieve the same results.
How do I set up a Sinatra MVC framework?
Setting up a Sinatra MVC framework involves several steps. First, you need to install the Sinatra gem. Then, you need to create a file structure for your application, which includes separate directories for your models, views, and controllers. After that, you can start building your application by defining routes in your controller, creating views to display data, and setting up models to interact with your database.
What is a controller in Sinatra MVC?
In Sinatra MVC, a controller is a component that handles the logic of your application. It receives requests from the user, interacts with the model to process these requests, and then sends the appropriate response back to the user. The controller is essentially the intermediary between the model and the view.
How do I use ‘include’ in my Sinatra application controller methods?
The ‘include’ keyword in Ruby is used to mix in modules into classes. This allows you to share methods between different classes. In a Sinatra application, you might use ‘include’ to add helper methods to your controller. These methods can then be used in any of your controller’s actions to perform common tasks.
How do I configure routes in Sinatra MVC?
Routes in Sinatra MVC are defined in the controller. Each route corresponds to a URL pattern and an HTTP method (like GET or POST). When a user sends a request that matches a route, Sinatra will execute the corresponding block of code. This block can interact with the model, render a view, or send a response directly to the user.
How do I create forms in Sinatra MVC?
Forms in Sinatra MVC are created in the view. They allow users to input data that can be sent to the server. When a user submits a form, the data is packaged into a request and sent to a route defined in the controller. The controller can then process this data and interact with the model as necessary.
How do I interact with a database in Sinatra MVC?
Interacting with a database in Sinatra MVC is done through the model. The model is responsible for creating, reading, updating, and deleting records in the database. This is often done using an Object-Relational Mapping (ORM) tool like ActiveRecord, which allows you to interact with your database using Ruby code.
How do I test my Sinatra MVC application?
Testing a Sinatra MVC application can be done using a testing framework like RSpec. This allows you to write tests for your models, views, and controllers to ensure they are working as expected. Testing is an important part of the development process, as it helps you catch and fix bugs before they become a problem.
How do I deploy my Sinatra MVC application?
Deploying a Sinatra MVC application involves transferring your code to a server where it can be accessed by users. This can be done using a platform like Heroku, which provides a simple way to deploy and manage web applications. Once your application is deployed, you can monitor its performance, fix bugs, and make updates as necessary.
Darren loves building web apps and coding in JavaScript, Haskell and Ruby. He is the author of Learn to Code using JavaScript, JavaScript: Novice to Ninja and Jump Start Sinatra.He is also the creator of Nanny State, a tiny alternative to React. He can be found on Twitter @daz4126.