Geohash toy: code released
A couple of weeks ago I wrote about a small toy app I'd written to explore geohashes. Now I've cleaned the code up a little, upgraded it to rails 3.1 and released it here on github. Enjoy.
A couple of weeks ago I wrote about a small toy app I'd written to explore geohashes. Now I've cleaned the code up a little, upgraded it to rails 3.1 and released it here on github. Enjoy.
For an app I've been building, I've been looking into geohashes. For those who don't know, the geohash format is a simple way to encode latitude and longitude into a single string. As an example, Nelson's Column in London (51.507794, -0.127952) has the geohash gcpvj0dyds.
Geohashes have a couple of interesting features. First, as you remove characters, you lose precision. gcpvj0dyds fairly accurately points to Nelson's Column; gcpvj0d represents the South-West of Trafalgar Square and some of the Mall; and gcpvj covers most of Central London, as well as Islington and King's Cross. A geohash doesn't really represent a point, but rather a bounding area within which a point may lie. The longer the geohash, the smaller that bounding area.
The other interesting property geohashes have is that nearby locations usually (but not always) share similar prefixes. So much of North London is in gcpv, while much of South London is in gcpu. However, due to the Prime Meridian passing through Greenwhich, South East London has the geohash u10h - wildly different than the other two.
This probably sounds a bit complicated. I was having trouble getting my head around the concept, so to try and get to grips with geohashes I've written a toy app that draws them on a map. To try it out, go to http://geohash.gofreerange.com, click the map, zoom and play. If you find it useful, let me know.
It's irritating to run gem commands like rake, cap, rspec and others, only to find they needed to be executed via bundle exec. As a simple solution, I use a simple zsh function, combined with aliases for commonly used commands.
Here's the function (which I've named be):
if [[ -a Gemfile ]]; then
bundle exec $*
else
command $*
fi
It's very simple. If there's a Gemfile in the pwd, it runs commands through bundle exec. Otherwise it just runs them.
I've combined this with some aliases for much less pain and less frustration:
alias rake='be rake'
alias cap='be cap'
alias rspec='be rspec'
In building #blue (sign up now!), one of the problems we faced was how to build json data in response to requests to our API. The typical rails solution would be to override #as_json in a model class, then write a controller like this:
class ContactsController < ApiController
responds_to :json
def show
respond_with Contact.find(params[:id])
end
end
I always prefer to keep my controllers as skinny as possible, so this looks like a great solution. The respond_with call takes care of converting the message to json and responding with the right Content-Type, all in a simple call. However it has a number of problems and disadvantages.
The biggest issue for our API is that rather than expose the id of each model, we've tried to encourage the use of the uri instead. So the json returned for a single contact (for example) looks like this:
{
"contact": {
"uri": "https://api.example.com/contacts/ccpwjc",
"name": "George",
"email": "george@handmade.org",
"msisdn": "447897897899",
"phone_number": "07897897899",
"messages": "https://api.example.com/contacts/ccpwjc/messages"
}
}
It doesn't just have a uri for the actual contact, but also for the messages belonging to that contact (and yes, I regret not calling that attribute messages_uri). Models can't generate uris, and shouldn't really be aware of them, so overriding #as_json doesn't work. In any case, the json structure is really presentation logic, not business logic. It doesn't belong in the model.
The solution we've used is to build a presenter for each model, solely responsible for building the json. Here's an example for a contact:
class ContactPresenter
include Rails.application.routes.url_helpers
attr_accessor :controller, :subject
delegate :params, :url_options, :to => :controller
delegate :errors, :to => :subject
def initialize(controller, subject)
@controller = controller
@subject = subject
end
def as_json(options = {})
{:contact => {
:uri => uri,
:email => subject.email,
:name => subject.name,
:msisdn => subject.msisdn,
:phone_number => subject.phone_number,
:messages => api_contact_messages_url(:contact_id => subject.id)
}
end
def uri
api_contact_url(:id => subject.id)
end
end
It's now simple to rewrite our controller to use the new presenter:
class ContactsController < ApiController
responds_to :json
def show
respond_with ContactPresenter.new(self, Contact.find(params[:id]))
end
end
The presenter above works well for a single model, but many of our API calls return a page of results. The /contacts for example returns all the contacts belonging to a user (of which there may be hundreds). Luckily it's simple to adapt this pattern to present pages like this. First, we change our original #as_json method slightly:
def as_json(options = {})
if options[:partial]
{
:uri => uri,
:email => subject.email,
:name => subject.name,
:msisdn => subject.msisdn,
:phone_number => subject.phone_number,
:messages => api_contact_messages_url(:contact_id => subject.id)
}
else
{:contact => as_json(:partial => true)}
end
end
This change allows us to call as_json with the options :partial. With the option, a hash of data is returned. Without, the same hash is returned, wrapped in another hash.
Next, add a page presenter:
class ContactPagePresenter
include Rails.application.routes.url_helpers
attr_accessor :controller, :subject
delegate :params, :url_options, :to => :controller
delegate :errors, :to => :subject
def initialize(controller, subject)
@controller = controller
@subject = subject
end
def as_json(options = {})
contacts = subject.map {|o| ContactPresenter.new(controller, o).as_json(:partial => true) }
{:contacts => contacts}.tap do |result|
if subject.previous_page
result[:previous_page_uri] = contacts_url(subject.previous_page)
end
if subject.next_page
result[:next_page_uri] = contacts_url(subject.next_page)
end
end
end
end
Finally, we can add an index action using this presenter:
class ContactsController < ApiController
responds_to :json
def index
respond_with ContactPagePresenter.new(self, Contact.paginate(:page => params[:page], :per_page => 50))
end
end
The code above is a very much simplified version of what we do in #blue. We have many controllers, and several different models, so in our actual code we've abstracted out as much common logic as possible. In reality, our contacts controller looks more like this:
class ContactsController < ApiController
before_filter :find_contact, :only => [:show, :update]
def show
present @contact
end
def index
present_page_of current_account.contacts
end
def create
@contact = current_account.contacts.build(attributes)
@contact.save
present @contact
end
def update
@contact.update_attributes(attributes)
present @contact
end
private
def find_contact
@contact = current_account.contacts.where(:_id => params[:id]).first
head :status => :not_found unless @contact
end
end
I think the code looks pretty clean. The clever stuff happens in the #present and #present_page_of methods, defined in the superclass:
class ApiController < ApplicationController::Base
protected
def present(instance, options = {})
presenter = presenter_class.new(self, instance)
options[:location] ||= presenter.uri if request.post? && subject.errors.empty?
respond_with presenter, options
end
def present_page_of(collection, options = {})
presenter = page_presenter_class.new(self, page_of(collection))
respond_with presenter, options
end
def page_of(collection)
collection.paginate(:page => params[:page], :per_page => 50)
end
def presenter_class
(self.class.name.gsub!("Controller", "").singularize + "Presenter").constantize
end
def page_presenter_class
(self.class.name.gsub!("Controller", "").singularize + "PagePresenter").constantize
end
end
The #present and #present_page_of methods handle determining the correct presenter to us, as well as paginating the collection where required. They still use rails build in #respond_with method, which helps provide the correct response headers for each request. As the ContactPresenter delegates #errors to its subject, if there are validation errors, #respond_with correctly returns a 422.
One further motivation for this pattern (other than moving presentation logic out of the model) is that should we want to release a new version of our API, we'll be able to get a lot of the way there simply by swapping which presenter is used. We started using this code about 8 months ago, and I'm still pretty happy with it. I hope you find something useful in it too.
Any comments or suggestions, please get in touch with me on twitter.
If you're an O2 customer based in the UK, you might be interested that #blue, a project we've built, has recently (re)opened for business. It's a soft launch, but feel free to tell your friends.
#blue gets copies of every SMS message you send or receive, and makes them available to you on the web. You can search your messages, read whole conversations, and even reply, all from the comfort of your browser.
As well as a nice-looking web UI, there's also an API that allows other apps to read and manipulate your messages and contacts (once you give them permission). Think automatic posting to twitter, or showing new messages as alerts on your desktop. The possibilities are endless.
Oh, and it's all free. All you need is an O2 phone.
If you think it sounds interesting, ask for a beta request. You should get an invitation very quickly. Once on board, please send me any suggestions and feedback. It's going to be interesting to see where we can take this project.