Sinatra Todo app with ActiveRecord – Step 1

By | 29/07/2014

What are we building

I started to like Sinatra, so I have decided to rebuild the good old Todo app (I did in Flask in this post) in Sinatra. U figured it would help me understand some of the basics (e.g. how to access a database for example).

In this post, we’ll be building an extremely simple todo application in Ruby Sinatra. We will use the following URLs to perform CRUD operations on the database.

GET /todos            Retrieves a list of all todo items in the database
GET /todos/index      Retrieves a single todo item from the database
POST /todos           Inserts a todo item in the database
PUT /todos            Updates a todo item in the database
DELETE /todos         Deletes a todo item from the database

Setting up some tools

A while ago, a colleague of mine told me about the Sinatra framework. As last months, I was pretty focused on Rails I was initially a bit sceptical to yet learn another framework. But boy…if I only knew this before. Sinatra is not new at all, it’s been around since a couple of years already (actually since 9th September 2007) and was creatred by Blake Mizerany. Sinatra is a small and flexible, free and opensource webframework written in Ruby, which does not follow the typical MVC pattern that is found in other frameworks like Rails for instance.

In this post, I wanted to create my good old todo app in Sinatra.

We will use the following gems for this (put the Gemfile in the rootdirectory):

  • gem “sinatra”: obviously 🙂
  • gem “sqlite3”: we will use sqlite in this application
  • gem “activerecord”: facilitates the access to our database
  • gem “sinatra-activerecord”: the bridge between Sinatra and the Activerecord interface
  • gem “shotgun”: development server so that we don’t need to reload the webapp each time
  • gem “tux”: ruby console

Our gem file looks like:

source :rubygems

gem "sinatra"
gem "sqlite3"
gem "activerecord"
gem "sinatra-activerecord"

group :development do
gem "shotgun"
gem "tux"
end

Obviously, next step:

$bundle install

We also need to create a rackup file. This would be needed if you want to deploy the app with a different Rack handler (Passenger, Unicorn, Heroku, …). Our config.ru file

require "./app"
run Sinatra::Application

Creating the database

With ActiveRecord it would be nice to take advantage of database migrations. Therefore, we could add a Rakefile (again under the root folder). The content is:

# Rakefile
require "./app"
require "sinatra/activerecord/rake"

The “sinatra/activerecord” gen comes with a couple of handy Rake tasks so we can execute them from the command line. To see which rake tasks are available, use the rake -T command. We will use the create_migration task to create our db migration file.

$ rake db:create_migration NAME=create_todos
db/migrate/20140811110015_create_todos.rb

You will now see a file called “20140811110015_create_todos.rb” under the db/migrate folder in your Sinatra app. We can now modify this file to create our database tables.

class CreateTodos < ActiveRecord::Migration
  def up
    create_table :todos do |t|
      t.text :content
      t.boolean :done
      t.datetime :completed_at
      t.timestamps
    end
      end
 
  def down
    drop_table :todos
  end
end

Executing the following command to do the migration. This command will now create a database called todo.db in the root folder, containing a table called todos.

$ rake db:migrate
== 20140811110015 CreateTodos: migrating ======================================
-- create_table(:todos)
   -> 0.0009s
== 20140811110015 CreateTodos: migrated (0.0033s) =============================

Adding some records manually

As we added the Tux gem in our Gemfile, this little handy tool allows us now to store records in our database from the command line.

$ tux
Loading development environment (Rack 1.2)
>> t = Todo.new(content: "First Todo item", done: false)
=> #
>> t.save
D, [2014-08-11T13:04:20.915230 #31773] DEBUG -- :    (0.1ms)  begin transaction
D, [2014-08-11T13:04:20.924566 #31773] DEBUG -- :   SQL (0.6ms)  INSERT INTO "todos" ("content", "created_at", "done", "updated_at") VALUES (?, ?, ?, ?)  [["content", "First Todo item"], ["created_at", "2014-08-11 11:04:20.916906"], ["done", "f"], ["updated_at", "2014-08-11 11:04:20.916906"]]
D, [2014-08-11T13:04:20.939124 #31773] DEBUG -- :    (13.9ms)  commit transaction
=> true
>> Todo.count
D, [2014-08-11T13:04:26.180073 #31773] DEBUG -- :    (0.2ms)  SELECT COUNT(*) FROM "todos"
> 1

Writing the app

What we did until now was some preparation work to get our app setup correctly. We didn’t write any decent code yet. As mentioned in the introduction, Sinatra does not really follow an MVC pattern. This means there is a lot of flexibility on how we structure everything. To keep things easy, we will just create an app.rb file under the root directory. Put the following code in the app.rb file:

require "sinatra"
require "sinatra/activerecord"
set :database, "sqlite3:todo.db"
 
class Todo < ActiveRecord::Base
end

# Get all of our routes
get "/" do
  @todos = Todo.order("created_at DESC")
  erb :"todo/index"
end  

Basically, this code will result in a list of all the todo items when the / route is requested from the webserver. Obviously, since we call the erb “todo/index”, we need to create an index.rb file in the views>todo directory. This file should contain the following code:

To do application

To do application

<ul> <% @todos.each do |todo| %> <li> <h2><a href="/todos/<%= todo.id %>"><%= todo.content %></a> <span><%= todo.created_at %></span> </h2> </li> <% end %> </ul>

To run the code, we will use shotgun. This tool allows us to rerun the application without manually having to restart the server. Note: shotgun is using port 9393 by default, so in your development enviroment, you will find the app through localhost:9393

$ shotgun
== Shotgun/WEBrick on http://127.0.0.1:9393/
[2014-08-11 13:56:34] INFO  WEBrick 1.3.1
[2014-08-11 13:56:34] INFO  ruby 2.0.0 (2014-02-24) [x86_64-linux]
[2014-08-11 13:56:34] INFO  WEBrick::HTTPServer#start: pid=32423 port=9393

The final result

Eventually, this is what we will get:

Screenshot from 2014-08-11 13:20:01

Admittedly, this is not something very nice and in a next post we will apply some bootstrap to it, so that it starts to look a bit nice. Stay tuned!!

Note: for this blog post I did not add routes to add, update or delete todo items. For those interested, this can be found here. Also, the full code is available on Github.