Singing with Sinatra - The Recall App

Welcome to Track 2 of Singing with Sinatra. In part one, we reviewed Routes, how to work with URI parameters, working with forms, and how Sinatra differentiates routes by the HTTP method they were requested by. Today, we're going to extend our knowledge of Sinatra by building a small database-driven app, "Recall," for taking notes/making a to-do list.

We're going to be using a SQLite database to store the notes, and we'll use the DataMapper RubyGem to communicate with the database. Run the following inside a shell to install the relevant Gems:

Depending on how you have RubyGems set up on your system, you may need to prefix gem install with sudo.


The Warm-Up

Let's jump right in by creating a new directory for the project, and creating the application file, recall.rb. Start it off by requiring the relevant gems:

Note: If you're running Ruby 1.9 (which you should be), you can drop the "require 'rubygems'" line as Ruby automatically loads RubyGems anyway.

And set up the database with the following:

On the first line we're setting up a new SQLite3 database in the current directory, named recall.db. Below that, we're actually setting up a 'Notes' table in the database.

While we're calling the class 'Note', DataMapper will create the table as 'Notes'. This is in keeping with a convention which Ruby on Rails and other frameworks and ORM modules follow.

Inside the class, we're setting up the database schema. The 'Notes' table will have 5 fields. An id field which will be an integer primary key and auto-incrementing (this is what 'Serial' means). A content field containing text, a boolean complete field and two datetime fields, created_at and updated_at.

The very last line instructs DataMapper to automatically update the database to contain the tables and fields we have set, and to do so again if we make any changes to the schema.


The Home Page

Now, let's create our home page:

At the top is a form to add a new note, and below it is all the notes in the database. To get started, add the following to the application file, recall.rb:

Important Note: Remove the dot ('.') in :.order. (WordPress is interfering with the code sample.)

On the second line you see how we retrieve all the notes from the database. If you've used ActiveRecord (the ORM used in Rails) before, DataMapper's syntax will feel very familiar. The notes are assigned to the @notes instance variable. It's important to use instance variables (that's variables beginning with an @) so that they'll be accessible from within the view file.

We set the @title instance variable, and load the views/home.erb view file through the ERB parser.

Create the views/home.erb view file and start it off with the following:

We have a simple form which POSTs to the home page ('/'), and below that is some ERB code serving as a placeholder for now.


Layouts

The HTML standards lot amongst you may have suffered a minor stroke after seeing that our home view file contains no doctype or other HTML tags. Well, there's a reason for that. Create a layout.erb file in your views/ directory containing the following:

The two interesting parts here are lines 5 and 18. On line 5 you see the first use of the <%= … %> ERB tags. <%= is different from the ordinary <% as it prints what is inside. So here we're displaying the whatever's in the @title instance variable followed by | Recall for the page's <title> tag.

On line 18 is <%= yield %>. Sinatra will display this layout.erb file on all Routes. And the actual content for that route will be inserted wherever the yield is. yield is a term which essentially means "stop here, insert whatever's waiting, then continue on".

Start up the server with shotgun recall.rb in the shell, and take a look at the home page in the browser. You should see content from the layout file, and the form from the actual home.erb view.


CSS

In the layout file we included two CSS files. Sinatra can load static files (eg. your CSS, JS, images etc.) from a folder named public/ in the root directory. So create that directory, and inside it two files: reset.css and style.css. The reset contains the HTML5 Boilerplate CSS reset:

And style.css contains some basic styling to make the app look pretty:

Refresh the page in your browser and everything should be more styled. Don't worry about this CSS too much; it just makes things look a bit prettier!


Adding a Note to the Database

Right now if you try submitting the form on the home page, you're going to get a route error. Let's create the POST route for the home page now:

So when a post request is made on the homepage, we create a new Note object in n (thanks to the DataMapper ORM, Note.new represents a new row in the notes table in the database). The content field is set to the submitted data from the textarea and the created_at and updated_at datetime fields are set to the current timestamp.

The new note is then saved, and the user redirected back to the homepage where the new note will be displayed.


Displaying the Notes

So we've added a new note, but we can't see it on the homepage yet as we haven't wrote the code for it. Inside the views/home.erb view file, replace the <%# display notes %> line with:

On the first line we begin a loop through each of the @notes (alternatively, we could have wrote for note in @notes, but using a block, as we are here, is a better practice). On line 2, we give the <article> a class of complete if the current note is set to complete. The rest should be pretty straight forward.


Editing a Note

So we can add and view notes. Now we just need the ability to edit and delete them.

You may have noticed that in our home.erb view we set an [edit] link for each note to what is essentially /:id, so let's create that route now:

We retrieve the requested note from the database using the ID provided, set up a @title variable, and load the views/edit.erb view file through the ERB parser.

Enter the following for the views/edit.erb view:

This is a fairly simple view. A form which points back to the current page, a textarea containing the content of the note and a checkbox which gets checked if the note is set to complete.

But look at the third line. Mysterious. To explain this, we need to side-track a little.

RESTful Services

You've heard of the two terms GET and POST.

  • GET: The most common. It's generally for requesting a page, and can be bookmarked.
  • POST: Used for submitting data and can not be bookmarked.

But GET and POST aren't the only "HTTP verbs" - there's two more you should know about: PUT and DELETE.

Technically, POST should only be used for creating something - like creating a new Note in your awesome new web app, for example.

PUT is the verb for modifying something. And DELETE, you guessed it, is for deleting something.

Having these four verbs is a great way to separate an app up. It's logical. Unfortunately, web browsers don't actually support PUT or DELETE requests, which is why you've likely never heard of them before.

So, getting back on track here, if we want to logically split our app up (which Sinatra encourages), we have to fake these PUT and DELETE requests. You'll see our form's action is set to post. The hidden _method input field which we've set to put on the third line lets Sinatra fake this PUT request, while actually using a POST. Rails, among other frameworks, do things a similar way.


Let us PUT

Now we've faked our PUT request, we can create a route for it:

It's all pretty simple. We get the relevant note using the ID in the URI, set the fields to the new values, save, and redirect home. Notice how on the fourth line we're using a ternary operator to set n.complete to 1 if params[:complete] exists, or 0 otherwise. This is because the value of a checkbox is only submitted with a form if it is checked, so we're simply checking for the existence of it.


Deleting a Note

In our edit.erb view, we added a 'Delete' link to what is essentially the path /:id/delete. Add this to your application file:

On this page we'll get confirmation from the user that they actually want to delete this note. Create the view file at views/delete.erb with the following:

Note that just like how we faked a PUT request by setting a hidden _method input field, we're now faking a DELETE request.


The DELETE Route

I'm sure you're getting the hang of this by now. The delete route is:

Try it out! You should now be able to view, add, edit and remove notes. There's just one more thing…


Marking a Note as "Complete"

Right now if you want to set a note as complete you have to go into the Edit view and check the box on that page. Let's make that process a bit simpler.

Back when we set up the main home page, we included a /:id/complete link on each note. Let's make that route now, which will simply set a note as complete (or incomplete if it was already set to complete):


Conclusion

You and Sinatra pull off one crackin' duet! You've very quickly written a simple web app which performs all the CRUD operations you'd expect an app to do. It's written in super-sexy-clean Ruby code, and is separated into its logical parts.

In the final part of Singing with Sinatra, the Encore, we'll improve on error handling, secure the app from XSS and create an RSS feed for the notes.

Note: You can browse the final project files for this tutorial over at GitHub.

Tags:

Comments

Related Articles