Friday, January 21, 2011
A feature that so many people seem to not know about the I18n API is the ability to lookup translations using a short “dot” syntax.
For example, say we are in a view located at: app/views/books/index/title
The view would look something like this:
<h1><%= t 'books.index.title' %></h1>
<p><%= t 'books.index.intro' %></p>
<ul>
<li><%= t 'books.index.point1' %></li>
<li><%= t 'books.index.point1' %></li>
<li><%= t 'books.index.point1' %></li>
</ul>
As you can see, we are repeating books.index quite a bit. Using this syntax will also make refactoring more cumbersome if we decide to move this view to another folder.
Rails provides us with a way to fix this. Under the “Organization of Locale Files” section of the I18n Rails Guide, it describes a “lazy” lookup for translations. Instead of having to put books.index at the start of each translation lookup, we can just use a dot. By using the dot, the view will automatically look at this views position in the application, and use that as the scope. So if you have a file located at: app/views/posts/comments/index the scope for translations of that file will be: posts.comments.index. Pretty cool huh?
Using this new syntax, we can clean up the above view like so:
<h1><%= t '.title' %></h1>
<p><%= t '.intro' %></p>
<ul>
<li><%= t '.point1' %></li>
<li><%= t '.point1' %></li>
<li><%= t '.point1' %></li>
</ul>
This is a really nice and DRY way of inserting translations into your application.
Thursday, January 20, 2011
In my previous blog post “The HTML5 History API”, I talked about using the new API to change the URL of a web page, without leaving the page itself. The problem with using the API directly, is that it only works in Google Chrome (at time of writing). I couldn’t find any documenation on whether or not this API will be introduced in IE9, so it would seem, we’re going to need a cross browser solution if we are going to keep our users happy.
Enter jQuery Address. From the jQuery Address website:
The jQuery Address plugin provides powerful deep linking capabilities and allows the creation of unique virtual addresses that can point to a website section or an application state. It enables a number of important capabilities including:
- Bookmarking in a browser or social website
- Sending links via email or instant messenger
- Finding specific content using the major search engines
- Utilizing browser history and reload buttons
I have expanded on the example I provided in my previous blog post, but I have changed the JavaScript to use jQuery Address, as apposed to the History API directly.
The example can be viewed at: http://ajax-pagination.heroku.com/ and the code can be downloaded at https://github.com/keithpitt/ajax-pagination.
The only changes to the application are the Javascript (that I will go through below) and the inclusion of the jQuery Address plugin.
I start off by setting the base url for the application. For the majority of cases, this will be “/”. But if you are doing something crazy with you app, and the root is actually “/my_application”, then you’ll want to set this value to: “/my_application”.
$.address.state('/');
We now need to define a function that is called when ever the browser state changes. I don’t want to an AJAX request if there are no query parameters in the URL, which is why I test for an empty object.
$.address.change(function(event) {
if($.isEmptyObject(event.parameters))
return;
$('#search').val(event.parameters.q);
$('#loading').slideToggle('fast', function() {
$.ajax({
url: event.path,
data: event.parameters,
success: function(html) {
$('#loading').slideToggle('fast');
$('#content').html(html);
}
})
});
});
Finally, we need to rewrite the bindings to the form and to the pagination links. Instead of calling history.pushState, we just need to call $.address.value, with the new URL (simple hey!)
$('form').live('submit', function() {
var action = $(this).attr('action'),
params = $(this).serialize();
$.address.value(action + '?' + params);
return false;
})
$('.pagination a').live('click', function() {
var href = $(this).attr('href');
$.address.value(href);
return false;
})
And thats it! We now have a cross browser, degradable, search engine crawlable solution to the state problem with web applications.
Thursday, January 20, 2011
This post is a continuation of my previous post “AJAX Broke The Web”. I recommend you read this first, to understand the scope of the problem we are trying to solve.
The HTML5 History API has been introduced as part of the new HTML5 Specification. It introduces 2 new methods to the window.history object, pushState and replaceState, and a new event to the window, onpopstate.
pushState
In essence, pushState adds a new state to the history stack.
// This object is passed to the onpopstate event when it is fired. You
// can fill this object with what ever you like.
var stateObject = {
title: 'This Awesome Post',
createdAt: '2010-10-10',
author: 'keithpitt'
};
// At the moment, most browsers are ignoring this property, so just fill it with
// the title of the new page. According to Firefox, they _may_ use it in the
// future.
var title = 'This Awesome Post';
// The full URL of the new page.
var url = '/posts/this-awesome-post';
history.pushState(stateObject, title, url);
replaceState
replaceState acts exactly the same as pushState, but it doesn’t add an entry to the history stack.
I cannot think of a good example of its usage in a real world situation, but I did see someone do a cool little URL animation by using it. It can be found on a blog post by Murray Picton.
A Practical Example
I have created an example on how to use the new HTML5 API using Ruby on Rails, and will_paginate. You can play with it at http://ajax-pagination-html5.heroku.com/
I have also provided the source code here: http://github.com/keithpitt/ajax-pagination-html5
It is a fairly straight forward implementation.
I first start by creating a function that I will use when it is time to update the content on the page using AJAX:
function update(url) {
$('#loading').slideToggle('fast', function() {
$.ajax({
url: url,
success: function(html) {
$('#loading').slideToggle('fast');
$('#content').html(html);
}
})
});
}
I then create some event handlers that will push a new state to the history when a user submits the search form, or clicks on a pagination link.
$('.pagination a').live('click', function() {
var href = $(this).attr('href');
history.pushState({ path: href }, '', href);
update(href);
return false;
});
$('form').live('submit', function() {
var form = $(this);
var url = form.attr('action') + '?' + form.serialize();
history.pushState({ path: url }, '', url);
update(url);
return false;
});
We also need to handle the onpopstate event. onpopstate is called when the page is loaded, so we need to check if we have access to the state object. If we do, then that means this history item was added via pushState.
$(window).bind('popstate', function(event) {
var state = event.originalEvent.state;
if(state)
update(state.path)
})
The last peice of the puzzle, is to tweak how we respond to the request in our Rails controller.
class SearchController < ApplicationController
def search
@query = params[:q]
@results = Game.search(@query).paginate :page => params[:page], :per_page => 10
render :layout => false if request.xhr?
end
end
And thats it!
Considerations
The example I provide here will only work in browsers that support the new HTML5 History API. At the time of writing, the only browser is Google Chrome. Firefox 4 will have support for this new API.
My next blog post will introduce a jQuery plugin that will simplify this process, and allow it to function cross-browser.
Additional Resources
Wednesday, January 19, 2011
AJAX broke the web.
It was 1999, and Microsoft had just created the XMLHTTP ActiveX control in Internet Explorer 5. It was a very small API, that allowed web applications to retrieve data from the server asynchronously in the background without interfering with the display and behavior of the existing page without leaving it. Shortly after its inital implementation by Microsoft, it was adopted by Mozilla, Safari, Opera and other browsers as the XMLHttpRequest JavaScript object.
The problem that AJAX introduced to web applications, was state. Before AJAX, a user would click a link and be taken to that page, changing the “state” of the application/site. With AJAX, a user clicks a link, and the new page is loaded asynchronously. From the browsers point of view, the state hadn’t changed - but as far as the user was concerned, it had. Because the browser didn’t know the state had changed, the back/forward butons didn’t work as expected either. This broke everyones head.
AJAX broke a golden rule of the internet, that many people still do today, but in other ways, and I won’t go into here. If you give a link to a friend, the page they see - should be exactly the same as what you see. There are some exceptions to this rule, but for the most part, it should be followed.
The last major problem it introduced was that when implementing AJAX into you web application, search engines could no longer crawl the pages. AJAX requires Javascript, the majority of search engine crawlers have no Javascript support, therefor, no crawling.
The solution that has worked fairly successfully so far, is to hack the “#” parameter (anchor) of the url. I won’t go into detail on how it worked, you can read more about it here. But in essence, it required changing the anchor of the page, and then monitoring it for change in Javascript. This method has always felt a little clunky to me, and I feel that we need a better solution.
The solution is now here, and its called the HTML5 History API. How does one use it? Stay tuned…
Thursday, December 16, 2010
I’ve been using Bluepill for a while now, but when it came to setting up a new production server at work last week, I remembered the pain I had in getting it working in the first place.
I knew there were 2 other players in the process monitoring space, God and Monit. I read that God had a pretty bad memory leak a while ago - and to be honest, I didn’t check to see if it had be fixed. I had done a quick 10 minute sprint and researched monit, and I decided I’d try that first. It was surprisngly easy to setup, and I really liked the syntax - simple and to the point.
I monitor 4 services on my production machine, delayed_job, searchd (Sphinx), memcached and nginx. Below is my configuration file with some commentary.
Before we jump into the config, there are a couple of things you need to know:
- monit needs to run as sudo if its to run correctly
- In order to use the monit CLI, you have to have the web server enabled in your monitrc file.
- If things aren’t working like they should, check the monit log. Its defined on a “per monitrc” basis. In my config below, I’ve put it in /var/log/monit.log
- If you want to check the syntax of your monitc: “sudo monit -t” is what you want.
- Running monit from a non sudo user with no password (usefull in deploy scripts), you’ll need to add the following to your sudoers file (accessed via: “sudo visudo”) ”deploy” being the user you want to run monit from:
“deploy ALL= NOPASSWD: /usr/sbin/monit”
Note: I have only tested this on Ubuntu 10.10, but I’m confident it’ll “just work” on other *unix platforms.
# Check every 10 seconds
set daemon 10
# Log all monit activity to this file
set logfile /var/log/monit.log
# Here I am using sendgrid to send alter emails
set mailserver smtp.sendgrid.net
username "your@username.com" password "yourpassword"
with timeout 30 seconds
# Set the email you want to have alerts sent to
set alert your@email.com
# This is where we setup the web interface. The web interface is
# also required for the monit CLI to work. Replace username/password
# with login credentials for the web interface.
set httpd port 2812 and
allow localhost
allow username:password
# This is where the magic happens.
# Here we specify the pidfile to monitor, and define start/stop
# commands. Monit's PATH variable when it starts processes is practically
# empty, so your best bet is to reference via its full path.
check process delayed_job
with pidfile /var/apps/myapp/shared/pids/delayed_job.pid
start program = "/usr/bin/env PATH=/usr/local/bin:$PATH RAILS_ENV=production /var/apps/myapp/current/script/delayed_job start" as uid deploy and gid deploy
stop program = "/usr/bin/env PATH=/usr/local/bin:$PATH RAILS_ENV=production /var/apps/myapp/current/script/delayed_job stop" as uid deploy and gid deploy
# Lets monitor searchd, which is used by thinking sphinx.
check process searchd
with pidfile /var/apps/myapp/shared/pids/searchd.pid
start program = "/usr/local/bin/searchd --pidfile --config /var/apps/myapp/current/config/production.sphinx.conf" as uid deploy and gid deploy
stop program = "/usr/local/bin/searchd --stop --pidfile --config /var/apps/myapp/current/config/production.sphinx.conf" as uid deploy and gid deploy
# Memcached aswell.
check process memcached
with pidfile /var/run/memcached.pid
start program = "/etc/init.d/memcached start"
stop program = "/etc/init.d/memcached stop"
# And for shits and giggles, nginx.
check process nginx
with pidfile /var/run/nginx.pid
start program = "/etc/init.d/nginx start"
stop program = "/etc/init.d/nginx stop"
Wednesday, November 17, 2010
Note: This was solution was hacked together in 30 minutes, theres a better way of doing it, I just havent followed through with it yet. Will update this article when I’ve done so.
We have a staging server at desksnear.me. Having to deploy to this server manually is annoying as hell - so I wanted to setup our CI server to automatically deploy to staging after a successful build.
After some research, I stumbled across this Hudson plugin: http://wiki.hudson-ci.org/display/HUDSON/Post+build+task. “This plugin allows the user to execute a shell/batch task depending on the build log output.Java regular expression are allowed.”
After you’ve installed the plugin, you’ll get access to a new “Post build task” checkbox under the “Post-build actions”. Fill in the blanks as per the image below:

Replace “cap staging deploy” with your command to deploy to staging. This solution only works with RSpec at the moment. The “Log text” should only match if all your tests pass - if they fail - that regular expression wont match, and therefor wont run your “cap staging deploy” command.
Wednesday, November 17, 2010
My team has already released an “offical” retrospective which can be found here but I wanted to write one up from my perspective - because thats how I roll.
Warren Seen approached me with the idea to do Rails Rumble. I jumped on the chance, because it was something I had wanted to do for quite some time. First thing we needed was 2 other people. We brought Alex Eckermann on board as the designer, and later on, we brought Bodaniel Jeanes on as our last developer. We called our team “The Rad Warlike Annex”, which is actually an anagram of “Keith Warren Alex” (we registered the name before Bo came onboard).
We had a couple of ideas, I wanted to do some sort of Myles Barlow review website, Warren was talking about a freelancer website-thingy (cant quite remember what it was) and Alex was talking about a coffee themed application. We couldn’t really decide on an idea until Bo came up with the desks sharing idea. We all instantly saw potential, and decided to go with that. I came up with the name “desksnear.me”, it was available, so we went with that.
We talked quite a bit about the idea before hand, and came up with a backlog of features. We used Pivotal Tracker to store and estimate the features.
The major problem that we were going to need to solve is not working in the same room as each other. Warren lives in Tasmania, Bo was in the US, and Alex lived quite a ways from me. Alex made an appearance at my place early on in the competition, but fell ill and split.
During the competition, we had a Skype channel open the entire time - this worked super well. We had constant communication over Skype - it was like we were in the same room for the duration of the competition. We also had our production and CI server setup first. The other guys were fantastic, and we worked fucking great together.
Just after the competition finished, search on the site fell over. There was much panicking, and I think there were a few tears (lets keep that between us though). It ended up being a problem with our Google communication pipeline. We reached our 2500 query limit with Google, which we had no idea would happen. We ended up talking to Linode and they helped us organise another IP address. Within an hour, we had reached the limit again.
We couldnt figure out why we kept falling over, we checked Google analytics and noticed a shit load of traffic coming from Y Combinator / Hacker News - surprisingly enough we were number #3. That day we had 8000+ uniques visitors.
We needed to solve this problem. We asked the judges if we could deploy a fix, we got a big fat NO. We asked if we could spin up a new server with the fix, and point public traffic to that, and judge traffic to the old one (with the bug). We got a no for that aswell.
We were planning on pulling out of the competition until @ThorMitchell (Product Manager of Google Maps) randomly poked Bo on Twitter asking us for the IP’s that we were using for the site. At around 10:00PM, he reset our usage, and bumped up the quota. <yoda>Saved we were</yoda>
At the end of the competition, we came 6th, and didn’t win any of the sponsor challenges. But coming 6th out of the hundreds of teams that entered is pretty fucking good. We are stoked with how we finished. We’ve had alot of interest in the app, and people are actually using it to make real bookings.
To date, our most popular workplaces are the Frontier Group in Perth AU, and Engine Yard in the US. We’ve had over 30,000+ unique visitors, 120,000+ page views, and less than 1% 500’s
We’ve got some great ideas coming up, so stay tuned to the Desks Near Me blog for more details.
Thanks to Bo, Alex and Warren for a win team. These dudes are champions.