Archive for Ruby

Tracking Outbound Links with Rails 3

To gain some insight on what interests users, I want to be able to track which links they click on.

There were a number of options I considered, and finally settled on using Javascript to update an underlying Click model. For analytics, I intend to aggregate the data offline. Thus the current aim is capture only.

To store the data, I created a simple model using:

rails g model Click url:string request_ip:string user_agent:string user_id:string

I decided not to store the all the request data, and instead just track the following:

  • URL - these are already normalized
  • Request IP - I aim to use this to figure out geo-location at a later date
  • User Agent - which browser used
  • User ID - a unique hash for the user, kept in a cookie

Rails kindly adds in the created_at column, which is the final key piece of data.

To generate the click data, I added the following into my application.js:

  $("a").live("click", function() {
    params = { click: { url: this.href } };
    $.post("/clicks", params);
    setTimeout('document.location = "' + this.href + '"', 100);
    return false;
  });

This posts the click through to the Click controller. In the controller, I added in the following:

  def create
    @click = Click.new(params[:click])
    @click.request_ip = request.remote_addr
    @click.user_agent = request.user_agent
    @click.user_id = cookies[:uid]

    respond_to do |format|
      if @click.save
        format.js { render :json => @click }
      else
        format.js { render :json => @click.errors }
      end
    end
  end

To generate the tracking cookie, I included the following in application.js:

  if($.cookie("uid") == null) {
    var tracking_id = $.md5($.now() + "-" + Math.ceil(Math.random() * 314159)); // Current time + random seed
    $.cookie("uid", tracking_id, { expires: 365 * 20, path: "/" });
  }

By using an MD5 hash of the current time and a random number, I am comfortable enough that it is unique enough for tracking purposes.

In this example, I am tracking all clicks on the application. For production use, I’ll filter this down to only the outbound article titles.

I’ve included a skeleton Rails 3 app that includes the code on GitHub. I used a jQuery plugin for cookies and MD5 hash generation, and these are included in the public/javascripts directory.

There are a few things that this misses, such as links clicked via RSS, via Twitter, and a heap of tests. I’m looking into Feedburner and bit.ly to see if I they fill in the missing metrics.

Making Emacs even more like TextMate

Much as I love TextMate, I’m still trying to get back to one editor. Here are a two more things to make Aquamacs even more TextMate like.

TextMate Minor Mode

Go to defunkt’s github page and follow the instructions.

This gives you access to some of the magical TextMate features. The ones I missed the most are:

  • ⌘T - Go to File
  • ⌘/ - Comment Line (or Selection/Region)
  • ⌘L - Go to Line

By adding in these few shortcuts, I find it much easier to jump between the editors.

Cucumber Support

A simple set of scripts to enable cucumber support into emacs. See the installation page for how to set it up. And don’t forget the pre-req.

Once installed, you get syntax highlighting for feature files, and snippet support.

You can also setup run targets, however that requires a few more dependencies.

Other Rails Files

Try the following in your .emacs to include additional syntax highlighting:

;; Rake files are ruby, too, as are gemspecs, rackup files, etc.
(add-to-list 'auto-mode-alist '("\.rake$" . ruby-mode))
(add-to-list 'auto-mode-alist '("\.gemspec$" . ruby-mode))
(add-to-list 'auto-mode-alist '("\.ru$" . ruby-mode))
(add-to-list 'auto-mode-alist '("Rakefile$" . ruby-mode))(add-to-list 'auto-mode-alist '("Gemfile$" . ruby-mode))
(add-to-list 'auto-mode-alist '("Capfile$" . ruby-mode))
(add-to-list 'auto-mode-alist '("Vagrantfile$" . ruby-mode))

RSpec XHTML Validation of Views

A great way to learn is to read other people’s code. And in that vein, Chris Lowe has a great post on suggested OSS Rails apps to look into.

One technique I picked up from simply_agile is to validate the HTML output by your views. That way, bad HTML isn’t introduced.

To start with, you need to install the assert_valid_xhtml plugin and the libxml-ruby gem.

In your spec/spec_helper.rb file, add the following inside the config block:

config.include ValidateXhtml

And at the end of the file, outside the config block, include the following:

describe "a standard view", :shared => true do
  it "should be successful" do
    response.should be_success
  end

  it "should be valid" do
    response.should be_valid_xhtml
  end
end

Now in your view specs, include the following line:

it_should_behave_like "a standard view"

And that’s it. Now each view will be tested for valid xhtml when you run your specs.

There are a lot of other great techniques scattered through the projects mentioned in the original post. And if you want more details on good practices for RSpec, check out the RSpec Book.

Machinist, AuthLogic & Cucumber

For a Rails app, I wanted to define rules about authentication using Cucumber and a bit of BDD. This went well, until I added in Pickle, Machinist, and AuthLogic; a few too many new things at once.

I first ran afoul of defining users and then password confirmation. This is easy to fix, as a Machinist spec is Ruby code:

Sham.email { Faker::Internet.email }
Sham.username { Faker::Internet.user_name }

User.blueprint do
  username
  email
  password 'secret'
  password_confirmation { password }
end

I also created a named admin user:

User.blueprint(:admin) do
  username { Sham.username + " -admin-"}
  admin { true }
end

I can then add some user specific step definitions for Cucumber using Webrat:

def user
  @user ||= User.make
end

def admin_user
  @admin_user ||= User.make(:admin)
end

def login(user)
  user
  visit '/login'
  fill_in("Username", :with => user.username)
  fill_in("Password", :with => user.password)
  click_button("Log in")
end

def logout
  visit '/logout'
end

Given /^I am a logged in user$/ do
  login(user)
end

Given /^I am logged in as an admin$/ do
  begin
    login(admin_user)
  rescue
    save_and_open_page
    raise
  end
end

Given /^I am not logged in$/ do
  logout
end

The three behaviors I want to define are for guest, non-admin and admin users. This gives me the following:

Scenario: Can't see new entry as guest
	Given I am not logged in
	When I go to path "/entries"
	Then I should not see "New entry"

Scenario: Can't see new entry as non admin
	Given I am a logged in user
	When I go to path "/entries"
	Then I should not see "New entry"

Scenario: Can see new entry as admin
	Given I am logged in as an admin
	When I go to path "/entries"
	Then I should see "New entry"

In writing this blog post, I cleaned up some of the features and found a few missing authentication paths. One further thing to ponder is what happens when someone visits your controller directly. I think you should also add defined behaviours for this, such as:

Scenario: Can't add new entry as non admin via controller
	Given I am not logged in
	When I go to path "/entries/new"
	Then I should be on the home page

A big thank you to Railscasts. Ryan has done several casts on Cucumber and AuthLogic that really helped get me started.

If you need to sham a Paperclip file with Machinist, check out Tim Riley’s blog. This article was also useful, although I found after I had most things working.

Learning Ruby on Rails

This year, my technology goal is to learn Ruby on Rails.

I often try to learn new programming languages, averaging around one a year. Sometimes it is a matter of coming back to a language I haven’t used in a while, other times it is learning a new framework.

After using Ruby for some scripting last year, I decided that Rails was a good choice to learn as a framework this year. Two things convinced me.

Firstly, lots of web innovation appears to arise from the Ruby community. Cucumber is one that has particularly caught my interest.

Secondly is Heroku. This is simply an awesome way to bootstrap a project. No mess, no fuss, code - deploy - repeat. It means I can launch an early version of the app with minimal investment of time or funding.

To dive in deep on a platform like Rails, I’ve taken a few steps:

  • Books – Admittedly still on order, I’m investing in some Rails books, as I find books provide broader coverage of topics than blogs alone.
  • Blogs – My RSS reader is being filled out with Ruby and Rails blogs. I subscribed to 20 odd to start with and will curate them over time.
  • Podcasts – The Rails community is served by a number of great podcasts and screencasts. Railscasts is a great entry point and I added others for broader coverage.
  • Twitter – Where I find bloggers or people that are influential in the Rails community, I add them to my Twitter list.
  • Friends – To start the process, I also asked a few friends who were Rails experts for suggestions of where to start. Pete put together quite a good list.
  • Tools – Installing Rails was a good start. I also added TextMate to my tool collection. There are some nice features that may cause me to make changes to my .vimrc one day.

All of that is the easy part. The next step is to actually write a bunch of Rails code. So far I’m tinkering with example code. However, I do have a larger project in mind which should provide the incentive to carry through. Details on that are for a later date.

If there are sources of good Rails info that you can recommend, please let me know either in comments, Twitter or email.