Here’s the first part of an overview guide to using Sinatra, Haml, and Postgres to quickly build a simple inquiry form web app & deploy it to Heroku

Example source code on Github: https://github.com/singlemind/example_sinatra_form

Getting started with Sinatra

Sinatra is a micro web framework that only requires a minimal amount of code to create a full-featured web app. Take for example this simple “Hello World” example (from http://www.sinatrarb.com/intro.html )

First install the Sinatra Gem. You’re using RVM, right?

gem install sinatra

Then create a file named myapp.rb

# myapp.rb
require 'sinatra'
get ‘/’ do
‘Hello world!’
end

Now fire it up with:

ruby myapp.rb

Point your web browser to http://0.0.0.0:4567 and look at that, wasn’t that simple? Commit that sucka to Git!

git init .
git add .
git commit -am “yearzero commit”

It’s time to spice things up

Being an avid Rails developer, I’ve grown accustomed to the ActiveRecord ORM. However, since we’re trying to keep things simple and minimal, let’s investigate another option, DataMapper— which is fast, thread-safe, and feature rich! Another Rails inspired paradigm is model class validations– one place that shines is with HTML forms that can do server-side validation on input data against model class attribute definitions. For example, if I had a Foo class with a name property, I could mark that property as required in the class definition so anytime an instance of Foo was saved (persisted) then the ORM would make sure the attributed name was valid. To begin we’ll start by using SQLite as our database (but we’ll move to Postgres when we want to deploy to Heroku).

Make sure you’ve got all the required datamapper gems installed:

gem install sinatra sinatra_more datamapper dm-core dm-timestamps dm-sqlite-adapter dm-migrations --no-ri --no-rdoc

Let’s begin by stubbing out a ResponseForm class which has the required attributes id, created_at, updated_at, tech_contact_name, tech_contact_email, tech_contact_phone, lms_used, lms_localhost, lms_test_env and two optional attributes other_lms_name, other_lms_version:

class ResponseForm
include DataMapper::Resource
property :id, Serial
property :created_at, DateTime
property :updated_at, DateTime
property :tech_contact_name, Text, :required => true, :default => ‘NULL’
property :tech_contact_email, Text, :required => true, :default => ‘NULL’
property :tech_contact_phone, Text, :required => true, :default => ‘NULL’
property :lms_used, Text, :required => true, :default => ‘NULL’
property :other_lms_name, Text, :default => ‘NULL’
property :other_lms_version, Text, :default => ‘NULL’
property :lms_localhost, Text, :required => true, :default => ‘NULL’
property :lms_test_env, Text, :required => true, :default => ‘NULL’end

Okay, now there’s a few things to do for datamapper so add this to your code:

# turn logging on
DataMapper::Logger.new($stdout, :debug)
# configure the SQLite DB
DataMapper::setup(:default, "sqlite3://#{Dir.pwd}/smc_form.sqlite")
# perform basic sanity checks and initialize all relationships
# call this once you’ve defined all your models…
DataMapper.finalize# auto_migrate will issue the necessary CREATE statements (DROPing the table first, if it exists)
# to define each storage according to their properties.
# After auto_migrate! has been run, the database should be in a pristine state.
# All the tables will be empty and match the model definitions.
#ResponseForm.auto_migrate!
# auto_upgrade tries to make the schema match the model.
# It will CREATE new tables, and add columns to existing tables.
# It won’t change any existing columns though (say, to add a NOT NULL constraint)
# and it doesn’t drop any columns.
ResponseForm.auto_upgrade!

Add a dash of Haml

Now we just need some HTML form fields into which we can enter some data. Enter Haml– Beautiful, DRY, well-indented, clear markup. We’ll be using this to create some view templates. Go ahead and install the gem:

gem install haml --no-ri --no-rdoc

Now create a new directory for view templates named “views” and inside that directory create a new file named “layout.haml”. The name and location of this file is convention (http://en.wikipedia.org/wiki/Convention_over_configuration). Here’s a quick look at how Haml syntax looks:

!!! 5
%html
%head
%title SMC Form
%body
#header
%h1 SMC Form
#content
=yield
%footer

This will be our main layout. Create another file named “index.haml” inside the view directory, this will be our HTML form:

%h3 Example inquiry form

%form(action=’/’ method=’POST’ id=’smcForm’)

%label Technical Contact Name:
%input(type=’text’ name=’tech_contact_name’ title=’Please enter some input (at least 2 characters)’ class=’required’ minlength=’2′)

%label Technical Contact email and/or IM:
%input(type=’text’ name=’tech_contact_email’ title=’Please enter some input (at least 3 characters)’ class=’required’ minlength=’3′)

%label Technical Contact phone:
%input(type=’text’ name=’tech_contact_phone’ title=’Please enter some input (at least 3 characters)’ class=’required’ minlength=’3′)

%label Name of LMS Used:
%input{:type => “checkbox”, :value => “sakai25”, :name => “lms_used[]”, :id => “lms_used1”, :class => “required”} Sakai 2.5
%input{:type => “checkbox”, :value => “sakai26”, :name => “lms_used[]”, :id => “lms_used2”} Sakai 2.6
%input{:type => “checkbox”, :value => “sakai27”, :name => “lms_used[]”, :id => “lms_used3”} Sakai 2.7
%input{:type => “checkbox”, :value => “sakai27”, :name => “lms_used[]”, :id => “lms_used3”} Sakai 2.7
%br
%input{:type => “checkbox”, :value => “moodle19”, :name => “lms_used[]”, :id => “lms_used4”} Moodle 1.9
%input{:type => “checkbox”, :value => “moodle20”, :name => “lms_used[]”, :id => “lms_used5”} Moodle 2.0
%input{:type => “checkbox”, :value => “moodle21”, :name => “lms_used[]”, :id => “lms_used6”} Moodle 2.1
%input{:type => “checkbox”, :value => “moodle22”, :name => “lms_used[]”, :id => “lms_used7”} Moodle 2.2
%br
%input{:type => “checkbox”, :value => “canvas”, :name => “lms_used[]”, :id => “lms_used8”} Canvas

%label Other:
%input(type=’text’ name=’other_lms_name’ title=’Please enter some input (at least 3 characters)’ minlength=’3′)

%label Version:
%input(type=’text’ name=’other_lms_version’ title=’Please enter some input (at least 3 characters)’ minlength=’3′)

%br

%label Is the LMS installed locally?
%input{:type => “radio”, :value => “yes”, :name => “lms_localhost[]”, :id => “lms_localhost1”, :class => “required”} Yes
%input{:type => “radio”, :value => “no”, :name => “lms_localhost[]”, :id => “lms_localhost2”} No
%input{:type => “radio”, :value => “idk”, :name => “lms_localhost[]”, :id => “lms_localhost3”} I don’t know

%label Is a testing environment available?
%input{:type => “radio”, :value => “yes”, :name => “lms_test_env[]”, :id => “test_env1”, :class => “required”} Yes
%input{:type => “radio”, :value => “no”, :name => “lms_test_env[]”, :id => “test_env2”} No
%input{:type => “radio”, :value => “idk”, :name => “lms_test_env[]”, :id => “test_env3”} I don’t know

%br
%br
%input(type=’button’ value=’Reset’ name=’reset’ id=’reset’)
%input(type=’submit’)

%div(id=’result’)

Spiffy! Now let’s tell the app to render the haml template when the root (“/”) path is requested:

get '/' do
haml :index
end

So now we need to accommodate for incoming posts from the HTML form. Simple create another block in your main app file (myapp.rb) and start it with the name of the HTTP verb, in our case, “post”:

post '/' do
params.each do |y|
puts y
end
@results = ResponseForm.new params
@results.created_at = Time.now
@results.updated_at = Time.now
if @results.save
“success!”
else
“fail! #{@results.errors.to_s}”
end
end

This will print every incoming parameter out to the console and create a new DB record.

Client-side validation

The jQuery validation plugin (http://bassistance.de/jquery-plugins/jquery-plugin-validation/) works great for this. Create a “public” directory in your project and add the jquery code there. Sinatra by default (http://en.wikipedia.org/wiki/Convention_over_configuration) serves static files out of the public directory. The jQuery form plugin (http://malsup.com/jquery/form/) allows us to do Ajax form posts. So go ahead and download that into the public directory. Don’t forget to also add the core jQuery library to your public directory. Add the following JS to your layout.haml file:

%script{:type => "text/javascript",
:src  => "/js/vendor/jquery-1.7.min.js"}
%script{:type => "text/javascript",
:src  => "/js/vendor/jquery.form.js"}
%script{:type => "text/javascript",
:src  => "/js/vendor/jquery.validate.js"}
%script{:type => “text/javascript”}
:plain
$(document).ready( function() {$(‘#smcForm’).validate({
submitHandler: function(form) {
jQuery(form).ajaxSubmit({
target: “#result”
});
$(“#smcForm :input”).attr(“disabled”, true);
}
});$(‘#reset’).click(function() {
$(‘#smcForm’).validate().resetForm();
$(‘#smcForm’).clearForm();
});} );

Sending an email with the results

How about if we wanted to send an email containing the results? Simple, use the pony gem!

gem install pony --no-ri --no-rdoc

Now add this after @results.save

Pony.mail :to => ‘test@example.org
:from => 'test_form@example.org',
:subject => 'TEST FORM',
:body => "#{params.inspect}"

At this point, you should have a fairly complete web form app running locally. Be sure to check out Part 2 to learn how to quickly deploy apps to Heroku.

The entire codebase is on the SingleMind Github page: https://github.com/singlemind/example_sinatra_form