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.
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.
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, DateTimeproperty :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. 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.nowif @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 works great for this. Create a “public” directory in your project and add the jquery code there. Sinatra by default serves static files out of the public directory. The jQuery form plugin 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 can be found here on the Singlemind Github page.