captured sparks

Archive for April, 2010

Quick debugging of validations with Rails

In an application I’m building, I have a tricky validation that needs to ensure that a booking created by the user:

  1. is not entirely within an existing booking
  2. does not have a start time within an existing booking
  3. does not have an end time within an existing booking
  4. does not span an existing booking

While testing the validation, it was really useful to add the following to the validation code:

existing_bookings.each do |b|
  Rails.logger.debug("#{b.id} - #{b.from} - #{b.to}")
end

This was valuable in identifying what bookings my AR query was pulling out during the validation.

Building an AJAX style calendar with Rails and Prototype

In this tutorial, I’m going to show you how to build an AJAX style multi-day calendar with Ruby on Rails and Prototype. The code assumes that you have a number of multi-day bookings or events that you need to display on a calendar. The final result looks like this:

Screenshot of calendar

We’ll start with the code for the controller. I’ve removed some of the code where I create the @hours variable. In my case, the calendar is displaying bookings relating to a particular aircraft:

def index
  @plane = Plane.find(params[:plane_id])
  @booking = @plane.bookings.new
  @hours = ["06:00","07:00","08:00"...]
  @date = Time.parse("#{params[:start_date]} || Time.now.utc")
  @start_date = Date.new(@date.year, @date.month, @date.day)
  @bookings = @plane.bookings.where('bookings.from BETWEEN ? and ? OR bookings.to BETWEEN ? and ?', @start_date, @start_date + 1, @start_date, @start_date + 1).order('bookings.from ASC').all
  if params[:start_date] && request.xhr?
    render :json => @bookings
  end
end

This retrieves all bookings for a particular aircraft on a given day: either today or the day passed in as a query string. The following HTML is used to display the calendar:

<section id="calendar">

  <ul id="dates-menu">
    <% for date in @start_date..@start_date + 6 %>
      <li><a href="/planes/<%= @plane.id %>/bookings?start_date=<%= Date.new(date.year,date.month,date.day) %>" class="dates-link"><%= date.strftime("%d %B") %></a></li>
    <% end %>
  </ul><!-- #dates-menu -->

  <br class="clear" />

  <div class="day">
    <div id="bookings">
      <img src="/images/ajax-loader.gif" id="ajax-loader"/>
    </div><!-- #bookings -->
    <div id="hours">
      <% @hours.each do |hour| %>
        <div class="hour"><%= hour %></div>
      <% end %>
    </div><!-- #hours -->
  </div><!-- .day -->
</section>

You’ll notice that the div with the id of bookings is populated with an AJAX-style loader.

Moving on to the javascript. I am no Javascript expert so it may be possible to refactor some of this to make it leaner. The first part of the code applies an active class to the first li element in the dates menu.

if ($('dates-menu')) {
  $('dates-menu').down(1).addClassName('active');
  var firstLink = $('dates-menu').down(1);
  if (firstLink.hasClassName('active')) {
    grabBookings(firstLink);
  };
};

The grabBookings function gets the bookings for a particular day using Prototype’s Ajax.Request.

function grabBookings(date) {
  $$(".booking").each(function(b) {
    b.remove();
  });
  if (!$('ajax-loader')) {
    $('bookings').insert("<img src='/images/ajax-loader.gif' id='ajax-loader' />");
  }
  new Ajax.Request(date.readAttribute("href"), {method:"get", evalJS:true, onSuccess:function(transport) {
    var bookings = transport.responseJSON;
    $('ajax-loader').remove();
    bookings.each(function(b) {
      var item = "<a href='/planes/" + b.plane_id +"/bookings/" + b.id + "'<div id='booking-" + b.id + "' class='booking' style='height:" + calculateBookingHeight(b.from,b.to) + "px;top:" + calculateBookingPosition(b.from,b.to) + "px;'><p><strong>From: </strong>" + formatTime(b.from) + "</p><p><strong>To: </strong>" + formatTime(b.to) + "</p></div></a>";
    $('bookings').insert(item);
    });
  }});
}

I’ve added this as a gist. In summary, this obtains the bookings for a particular day and the controller returns the bookings in JSON format. I then iterate over each booking and insert it into the calendar. The two key functions used in the code are to calculate the position of the booking to ensure it aligns with the appropriate time and the height of the booking to ensure it covers the appropriate time period. Looking at the function to calculate the height first:

function calculateBookingHeight(from, to) {
  var today = new Date();
  var end = new Date(today.getUTCFullYear(),today.getUTCMonth(),today.getUTCDate(),23);
  var start = new Date(today.getUTCFullYear(),today.getUTCMonth(),today.getUTCDate(),6);
  var from = parseDate(from);
  var fromUTC = new Date(from[0],(from[1] - 1),from[2],from[3],from[4],from[5])
  var to = parseDate(to);
  var toUTC = new Date(to[0],(to[1] - 1),to[2],to[3],to[4],to[5])
  if (fromUTC > start && toUTC < end) {
    var difference = (toUTC - fromUTC) / 120000;
  } else if (fromUTC > start && toUTC > end) {
    var difference = (end - fromUTC) / 120000;
  } else if (fromUTC < start && toUTC < end) {
    var difference = (toUTC - start) / 120000
  } else {
    var difference = 510
  }
  return difference;
}

The key elements here are the use of the figure 120000 and the multiple if statements. The height of 1 hour on the calendar is 30px. Thus, if a booking is 1 hour long, that is equivalent to 3,600,000 milliseconds. When divided by 120000, this produces a result of 30 i.e. 30px. The if statement is to deal with the situation where bookings either take up the whole day or cross with the previous or next day. The height is adjusted accordingly.

function calculateBookingPosition(from, to) {
  var today = new Date();
  var today = new Date(today.getFullYear(),today.getMonth(),today.getDate(),6,0,0)
  var start = parseDate(from);
  var fromUTC = new Date(start[0],(start[1] - 1),start[2],start[3],start[4],start[5])
  var from_hours = (parseFloat(giveMeTime(from).split(":")[0]) - 6) * 30
  var from_minutes = (parseFloat(giveMeTime(from).split(":")[1]) / 60)
  var minutes = parseInt(from_minutes * 30)
  if (fromUTC > today) {
    var top = parseInt(from_hours + minutes)
  } else {
    var top = 0
  }
  return top
}

Again, based on the height of 30px per hour, this function calculates the absolute position from the top of the calendar at which the booking needs to be displayed.

You will note a number of functions within these 2 functions that are used to extract certain aspects of the date and time of the booking. Based on an input in the format of 2010-04-25T12:00Z, I use the following function to extract the time in format 12:00.

function giveMeTime(time) {
  var time = time.split("T")[1].gsub(/[A-Za-z]/,"")
  return time
}

Leave a comment if you spot any obvious flaws in my design or with suggestions for improving the code. I’m also considering releasing this as a plugin for Rails if there is any interest?

Moving your Wordpress database to production

When I moved to Wordpress, I manually imported all my existing blog posts from the custom database into Wordpress. This meant that when it came to moving to production, I had to import all posts into the production database. The steps I took to do this were:

  1. Install Wordpress as normal on your production server.
  2. From your development database, export all tables except the users and options tables.
  3. On your production database, drop all tables except users and options.
  4. Import your development sql file into your production database.

One caveat: If your blog has multiple authors, this method is unlikely to work. On my development machine, the posts were imported as admin and therefore matched the user setup on the production machine.

Blue Sky Resumes

The website for blue sky resumes is brilliant. They seem like they really care about their potential clients; even going so far as to recommend competitors if the visitor doesn’t think blue sky would be the right fit.

They offer an online resume writing course (free), which has the potential to reduce their business and 80 sample resumes (free), which again have the potential to reduce their business.

This is a brilliant way to build brand loyalty and I know that if I wanted my resume reviewing, I would certainly consider blue sky.

DISCLAIMER: This is not a sponsored post.

Implementing cache-control with Apache 2

With Google’s decision to include page speed in the factors it uses to decide ranking, it’s more important than ever to increase the speed with which your page loads. The Google Page Speed plugin in for Firebug is a great way to identify what is slowing down your page and where improvements can be made.

One of the areas that I could improve on captured sparks was to explicitly set how I wanted my content to be cached. This site is running on Apache 2 so these instructions are for that versions:

cd /etc/apache2/mods-enabled

Check to see if mod-headers and mod-expires are enabled. If not:

cd /etc/apache2/mods-available
sudo a2enmod headers
sudo a2enmod expires
sudo /etc/init.d/apache2 force-reload

If these modules are not compiled, you will need to do so.

Although I have access to my httpd.conf file, I’ve implemented cache-control in my sites .htaccess file.

<FilesMatch "\.(flv|gif|jpg|jpeg|png|ico|swf)$">
   Header set Cache-Control "max-age=2592000"
</FilesMatch>

This sets the expiry of images and flash to 1 month from the request date as these are unlikely to change particularly frequently.

<FilesMatch "\.(js|pdf)$">
   Header set Cache-Control "max-age=604800"
</FilesMatch>

This sets the expiry of javascript and pdfs to one week from the request date.

<FilesMatch "\.(html|htm|css|txt)$">
   Header set Cache-Control "max-age=43200"
</FilesMatch>

Finally, this sets HTML, stylesheets and text files to expire 1 day from the request. As I am making changes to my site every couple of days at the moment (refining is the word), I need a short expiry time.

Screenshot of Firebug showing expiry date

Inspecting an image with Firebug, you can see that they will now expire 1 month from today at which point the browser will request the file from the server again.

Adding a Twitter feed via jQuery

So, as part of my ongoing development of the site, I’ve added a Twitter feed. The instructions here were great for getting a basic feed.

I’ve then expanded on the javascript used to linkify normal links to incorporate links to users and hashtags:

String.prototype.linkify = function() {
	var text = this.replace(/([A-Za-z]+:\/\/[A-Za-z0-9-_]+\.[A-Za-z0-9-_:%&\?\/.=]+)/,"<a href='$1'>$1</a>");
	var text = text.replace(/(@([\w]+))/g,"<a href='http://twitter.com/$2'>$1</a>");
	var text = text.replace(/(#([\w]+))/g,"<a href='http://twitter.com/#search?q=$2'>$1</a>");
	return text
};

The first replace deals with normal links. The second deals with usernames by capturing each username and then using backreferences to construct the link. The same method is used for hashtags.

Firefox and block level links

Has anyone else had any problems trying to wrap block level elements in an a tag in Firefox for Mac.

In my case, I was trying the following:

<a href="">
<li><img src="" /></li>
</a>

Unfortunately, Firefox for Mac seems to pre-emptively close the a element and then wraps the img in an a tag.

I’ve checked on my Windows machine though and the elements work fine.

Moving to Wordpress

So, I’m back and have just moved my blog to Wordpress. I had been running on a custom system for a couple of years but never got round to developing it and it made sense to make the move.

I’ve set Wordpress up so that I can deploy with Capistrano following the instructions at Adam Hunter’s blog. I’ve set out below a few more recipes I added to the deploy code to take into account the plugins I am using. These were mainly to ensure I could continue running a development version on my Mac.

For those using a Sitemap generator, generate the Sitemap in the shared directory and use the following to symlink it:

desc "Make symlink for sitemap"
task :sitemap do
    run "ln -nfs #{shared_path}/sitemap.xml #{release_path}/public/sitemap.xml"
end

If you are using the Smart Archives plugin, the archives.txt file will be in different places on your dev machine. Make a duplicate file called smartarchives-online.php and set the archives.txt location to the appropriate place. Then use the following:

desc "Copy online smart archives file"
task :archives do
  run "cp #{release_path}/public/wp-content/plugins/smartarchives-online.php #{release_path}/public/wp-content/plugins/smartarchives.php"
  run "chmod 775 #{release_path}/public/wp-content/archives.txt"
end

Finally, to remove the wp-config.php file, use the following:

desc "Remove dev config file"
task :remove_dev_config do
  run "rm -rf #{release_path}/public/wp-config.php"
end

Thankfully, as I did not have too many entries on my blog, setting up 301 redirects has not been too much of a task. I’ll have to wait and see when Google next crawls the site, if I’ve picked all the changes up.

My Space Kids

Background

The site was needed to act as a brochure for parents looking to place their kids into activity camps during school holidays. It also needed to enable parents to complete booking forms to send with payment to reserve their child’s place.

Intatec

Background

The site was primarily developed to enable prospective customers to search the vast range of products that Intatec offers. Large images on the front page highlight the quality of the image and the individual product pages show the approvals that each product holds.