Blog

Organise Your Models

We’ve recently taken on a few large projects at Stac and one thing that’s always bothered us was how large your models can get. This becomes more of a problem if you follow the skinny controller, fat model rule, as you’re wrapping up a lot of your business logic within model methods.

Code organisation might not be an immediate issue in Rails as it lays down some simple organisational conventions from the get-go, but that doesn’t mean there’s an excuse for letting things get out of hand.

One thing that works particularly well with convoluted models is wrapping up chunks of code into modules. There are a few benefits of this approach; testability, maintainability and readability.

Let’s take a simple example of a user that wants to be able to add a remove friends in an application. Your User model might look something like this:

            # app/models/user.rb
            class User < ActiveRecord::Base
              # ... other user methods ...
              
              has_many :friendships, :foreign_key => :initiator_id
              has_many :friends, :through => :friendships, :source => :recipient
              
              def befriend(other)
                # Some way of befriending another user
                self.friends << other
              end
              
              def unfriend(other)
                # Some way of removing an existing friend
                self.friends.delete(other)
              end
              
              def has_friend?(other)
                # Some way of checking the inclusion a
                # user in another users friend list
                self.friends.include?(other)
              end
              
            end
            

And your Friendship model might look something like this:

            # app/models/friendship.rb
            class Friendship < ActiveRecord::Base
              belong_to :initiator, :foreign_key => :initiator_id, :class_name => "User"
              belong_to :recipient, :foreign_key => :recipient_id, :class_name => "User"
            end
            

This can be fine for a smaller project, but as it becomes more complex you might also have methods which deal with authentication, authorisation and modify news feed content or information related to their profile. What we need to do is group related methods into smaller units. It’s best to be strict about these conventions earlier on to minimise the impact of refactoring against the code base later.

So how do we go about organising our code? Firstly create a file at lib/models/user/friendship_methods.rb and define a module to contain our code in like so:

            module Models
              module User
                module FriendshipMethods
                  extend ActiveSupport::Concern
                  
                  included do
                  end
                  
                  module ClassMethods
                  end
                  
                  module InstanceMethods
                  end
                end
              end
            end
            

Here we’re using ActiveSupport::Concern which cleans up the convention of having class and instance methods mixed in to the including class. Anything we include within the included block will be class evaluated (class_eval) on the including class.

Now we can move our friendship methods into the module:

            # lib/models/user/friendship_methods.rb
            module Models
              module User
                module FriendshipMethods
                  extend ActiveSupport::Concern
              
                  included do
                    has_many :friendships, :foreign_key => :initiator_id
                    has_many :friends, :through => :friendships, :source => :recipient
                  end
                  
                  module InstanceMethods
                  
                    def befriend(other)
                      # Some way of befriending another user
                      self.friends << other
                    end
            
                    def unfriend(other)
                      # Some way of removing an existing friend
                      self.friends.delete(other)
                    end
            
                    def has_friend?(other)
                      # Some way of checking the inclusion a
                      # user in another users friend list
                      self.friends.include?(other)
                    end
                  end
                end
              end
            end
            

…and include it in our User class:

            # app/models/user.rb
            class User < ActiveRecord::Base
              include Models::User::FriendshipMethods
              
              # ... other user methods ...
            end
            

Much better. As mentioned before one of the benefits of splitting your code up this way is how it aids unit testing. We can easily split our specs up into their relevant counterparts:

            # spec/lib/models/user/friendship_methods_spec.rb
            describe User, '(Friendship Methods)' do
              
              it "should have a has_many association on friends through friendships"
              it "should have a has_many association on friendships"
              
              context '#befriend' do
                it "should be able to create a friendship between another user"
                it "should do nothing if a friendship already exists"
              end
              
              context '#unfriend' do
                it "should be able to remove an existing friendship"
                it "should do nothing if a friendship doesn't exist"
              end
              
              context '#has_friend?' do
                it "should return true if a friendship exists"
                it "should return false if no friendship is found"
              end
            end
            

As your project grows, you’ll begin to have groups of well organised, well tested units. As your class grows you’ll easily be able to maintain segments of the code from within their modules. Our User class could have many more modules encapsulating different functionality, but our class body remains concise:

            class User < ActiveRecord::Base
              include Models::User::AuthenticationMethods
              include Models::User::AuthorisationMethods
              include Models::User::ProfileMethods
              include Models::User::FriendshipMethods
              include Models::User::FeedMethods
              
            end
            

How do you organise your code? Let us know in the comments.

Posted on May 3rd, 2011 by Josh

Meet The Ghostly Map

We recently contacted Ghostly International about creating a clean visualisation related to their artists tour date schedules. What originally started as a side project ended up getting deployed to their live site a couple of weeks later. Now that the dusts settled we thought we’d talk briefly about the project.

The application itself is a simple Rails 3 application. It’s deployed on Heroku and pulls its data from an API provided by Ghostly. To keep API caching outside of the request cycle dates are refreshed every day using a Rake task scheduled using Cron.

To ensure the application remains snappy under load we also cache artist and tour data in memory. Should we ever need to alter this strategy we can easily swap it out for Memcached or something more heavy weight thanks to Rails’ cache store API.

The client is a simple Javascript application powered by jQuery. If we were to build the application again we would go with something like Backbone or Spine for a little more structure. This became evident as development progressed as a lot of the logic revolved around the actual data, and was not particularly bound to the DOM.

All of the dynamic views (such as the artist information panel) are built from Underscore Templates and are compiled when required by the application. Keeping all of the data local helped make everything more responsive, and although there are obvious costs involved (when the application initially loads) we think it’s a good cost to pay considering the performance thereafter.

Artist tour dates are plotted on the map by the latitude and longitude of the venue. This is figured out using a logging function powered by GeoKit. We use a couple of attributes from a date listing to attempt to locate the venue, falling back to a rough approximation if the exact location of the tour date cannot be found.

Of course, all this wouldn’t be possible without Google’s excellent Maps API, which does the heavy lifting when rendering, plotting and tweening tour date locations on the map.

To make the interface even more useful we allowed filtering of artist performances by passing in a URL parameter. This would find the nearest tour date for a given artist (disregarding previously played dates). This allowed each artist to be linked to from the artists page on the Ghostly site.

Although the application should work fine in any browser we payed particular attention to Webkit based browsers, especially Mobile Safari. The application renders beautifully on an iPad, and we’re looking at optimising it for iPhone in future releases. There’s plenty of room for optimisation though, the first of which would be looking at using something like Local Storage to actually cache the tour dates entirely on the client.

This is the first of many side projects we’re hoping to release in the coming months. There’s tonnes of new technology we want to get our hands on, and with new API’s coming out all the time we’re definitely not short of ideas.


Posted on Apr 18th, 2011 by Josh

Weekend Project: Facebook Shares Counter

Over the weekend a Hacker News post caught my eye showing a simple way to use the Facebook Graph API to get basic statistics on link shares through Facebook. We thought this could be something really useful to our clients, being able to view a links share count using the Facebook Graph could really help them view the effectiveness of social campaigns.

So useful in fact that we quickly knocked up an interface to make it even simpler. You can view the result at shares.labs.wearestac.com.

It’s not perfect, there are some URL’s which don’t play too nicely (or Facebook returns no results) – but it’s interesting to see the ones that do. Underneath it’s just a simple application which calls the Facebook Graph object for a given URL. For example:

              http://graph.facebook.com/http://mashable.com
            

Would provide us with the share count (and comment count where applicable) for the URL object:

              { "id" : "http:\/\/mashable.com\/", "shares" : 5237, "comments" : 40 }
            

Note that you can also permalink a query, using the link provided below each result.

Let us know if you find any quirks, we’re still experimenting with the concept.

Posted on Mar 21st, 2011 by Josh