home / blog

AJAX contact form with Ruby CGI backend

In 2013 I vowed never to write a line of PHP again, it’s dirty and bad for your CV. When it came to implementing a basic AJAX form for a small campaign website I decided to use my new friend Ruby, but wasn’t as straightforward as I had hoped…

The site is otherwise static so using rails seemed like a bit of overkill, along with the deployment hell it causes with dreamhost. Yes the performance of CGI isn’t great compared to PHP with fast CGI mode, but we’re only talking small traffic volumes.

The ruby CGI script reads the POST data from standard input, sends the necessary headers. The whole thing is wrapped with an exception handler to avoid returning HTTP 500 errors that would otherwise occur with a non-zero exit status.The script either returns JSON {success:true} or {success:false, message:’…’}

With dreamhost it is important to tell the mail gem to use sendmail rather than SMTP otherwise you’ll get into all sorts of authentication issues.

Also with shared hosting you need to setup a custom gem directory e.g ~/.gems, gem install mail, and set the GEM_HOME variable before the call to require ‘mail’

#!/usr/bin/ruby

ENV['GEM_HOME'] = "/home/bob/.gems"

require 'mail'
require 'cgi'
require 'json'

# Must get stdin before CGI object created
stdin = STDIN.read
cgi = CGI.new
puts cgi.header("type" => "application/json")

begin
  data = JSON.parse(stdin)
  mail = Mail.new do
    from     'user@example.org'
    to       'user@example.org'
    subject  'Feedback from example.org'
    body     "Name:#{data['name']}\nComments:#{data['comments']}" 
  end
  mail.delivery_method :sendmail

  mail.deliver

  puts ({success: true}).to_json

rescue Exception => e
  puts ({success: false,message: e.message}).to_json
end

I used .htaccess to hide /app/contact.rb behind /app/contact

Options +ExecCGI
AddHandler cgi-script .rb
<ifModule mod_rewrite.c>
    RewriteEngine On
    RewriteRule ^contact$ contact.rb [L]
</ifModule>

The JavaScript reads the values from two input fields, converts to JSON and posts to /app/contact. The result from the HTTP POST is also JSON so that response is handled and relevant feedback given to the user.

var result = $('#contact .result');
var button = $("#contact button.send");

var success = function() {
  result.removeClass('failure');
  result.addClass('success');
  result.text("Your feedback has been sent. Thank you");
};

var failure = function() {
  result.removeClass('success');
  result.addClass('failure');
  result.text("There was a problem sending your feedback. Please try again later");
};
button.click(function() {
  var data = {
    name : $('#contact .name').val(),
    comments : $('#contact .comments').val()
  };
  button.attr('disabled', 'disabled');
  $.ajax({
    url : 'app/contact',
    type : 'POST',
    data : JSON.stringify(data),
    dataType : 'json',
  }).done(function(result) {
    if (result && result.success) {
      success();
    } else {
      failure();
    }
  }).fail(function(error) {
    failure();
  }).always(function() {
    button.removeAttr('disabled');
  });
});
This entry was posted in geek and tagged , . Bookmark the permalink.

Leave a Reply

Your email address will not be published.