trajectory

notes from the field...

Customised Redirect After Sign in With Devise

Devise is a very popular Ruby gem used in Rails for authenticating access, and allowing Rails applications to quickly set up common access behaviours like, “Forgot Password?”.

It took me a little while to find out how to redirect a devise user to a specific path after signing in, or registering (signing up) for an account.

The point of the exercise, is to be able to create a your own registration flow which doesn’t correspond to the usual/normal approach adopted by devise.

Typically, a registration flow in devise is:

  • sign up with an email address
  • you’re sent a confirmation email, and cannot progress until you:
  • follow the confirm link in the email, where you are prompted to:
  • set a password and then you’re in!

I wanted to create a different series of steps

  • sign up with an email address
  • you’re sent a confirmation email, and then:
  • in parallel progress to other steps needed to subscribe for a service (eg payment)
  • you can follow the confirm link in the email at any time

So, first we should write a test:

rspec registrations controller test
1
2
3
4
5
6
7
8
9
10
before do
  @request.env['devise.mapping'] = Devise.mappings[:user]
end

describe "when I register" do
  it "should redirect to a payment page" do
    post :create, :user => { :email => 'some@email.com' }
    response.should redirect_to(payment_path)
  end
end

The following block achieves a simple yet powerful thing. It loads up the devise scope for the user into the request, allowing routes and devise modules to be available for the test to run properly. Without this, the devise routes are not available, and the test fails with a message asking whether a devise_scope for the user has been set at all in the routes.

load the devise scope for the user
1
2
3
before do
  @request.env['devise.mapping'] = Devise.mappings[:user]
end

The remainder of the test posts to the create action of the registrations controller, after which assert that the response should be a redirect to the required payment path.

To make the test pass:

overide a specific method in the registrations controller
1
2
3
4
5
6
7
8
class RegistrationsController < Devise::RegistrationsController

  protected

  def after_inactive_sign_up_path_for(resource)
    payment_path
  end
end

This method is useful for redirecting users who have registered, and have not yet confirmed their account.

Once they have confirmed their account, should you choose to redirect them to a specific path, then again write this test:

rspec confirmations controller test
1
2
3
4
5
6
7
8
9
10
11
12
before do
  @request.env['devise.mapping'] = Devise.mappings[:user]
  @user = User.create! Factory.attributes_for(:user)
end

describe "when I confirm my account" do
  it "should redirect to a status page" do
    post :confirm_account,
         :user => { :confirmation_token => @user.confirmation_token }
    response.should redirect_to(status_path)
  end
end

I’m using FactoryGirl above to create valid attributes for a user. I set up the devise scope just as before, and then I post the confirmation token to the confirm_account action of the confirmations controller asserting that this should redirect the user to a status_path.

To achieve this I modify the confirmations controller thus:

confirmations controller
1
2
3
4
5
6
7
8
9
class ConfirmationsController < Devise::ConfirmationsController

  [ ... some methods removed ... ]

  protected
  def after_sign_in_path_for(resource)
    status_path
  end
end

Two Brilliant Weeks on the Road

I just finished a two week trip to the US, spending most of my time in Boulder Colorado, and San Francisco.

Most of the time in San Francisco was spent at Dreamforce meeting with the small, but growing group of developers who build bespoke applications that integrate with Salesforce.com. The most notable part of this experience was the chance to actually meet in person so many people who I have talked to over skype or over email, or via pull requests on github.

There’s really no substitute to meeting people in person, and I left San Francisco that much more in tune and in touch with the community. I missed Metallica … yes … but um I saw a car that sends microblogs to its owner. Dreamforce brought home to me the scale at which things happen in the US, and the kind of engagement and enthusiasm that people show towards their vocation.

I then attended Rocky Mountain Ruby in Boulder, an experience that could not be more different than that of Dreamforce. 200 attendees, as opposed 45000. A single track conference held in a lovely old theatre, as opposed to a smorgasboard of options held across multiple city blocks. Both were fun and professionally constructive, but I really identified with and enjoyed the Ruby conference a little more.

Delivering a superb event, Marty Haught ran the conference really well. We were entertained, educated, watered, beered, and fed. A really excellent conference.

I have always had an affinity with Boulder, and I got to stick around for a week after the conference. My friend Damien (friendly Frenchman based in Sydney) and I rode mountain bikes, exploring some awesome trails. We sampled some awesome food and drank some really amazing boutique microbrew beer. Get there, its a great town. I am coming back for sure - perhaps to stay for a while.

During the week, I met with about 7 or 8 businesses that are all creating something new. Small companies, tech startups, with a small footprint and an audacious goal. A significant observation about Boulder is that it is full of enterprising folk. From Celestial Seasonings Tea, to Crocs, to some of the best little tech startups around. Oh and I believe the US atomic clock lives in Boulder…sweet.

Between the people, the tech, the trails, the beer, the food and the beer, I was really taken by the town. My heartfelt thanks to those who made it such an awesome stay. I hope that you guys will remember the dude from NZ that turned up, and know you’re always welcome out our way.

Heading home, we made the mandatory pilgrimmage to REI (as all Antipodeans do), and visited a great friend of mine who lives near Denver. A lovely evening of more fine company, food and you guessed it … microbrew beer.

Sitting at SFO airport now, getting set to board the plane. I’m dying to see my family - being away from them has been the toughest part of the journey, however I got to do my thing, and I reckon that our little family will be better off for it in the long term …

Cherry Picking Commits With Git

Today, I spent a lot of time cherry picking my way through a bunch of commits.

Before explaining further - this is the scenario we were in.

  • over about 3 weeks we’ve had to vendor a Ruby gem so as to modify and extend it rapidly in our Rails app
  • we’ve done this quickly and without tracking changes in a fork of the gem (bad!)
  • we needed to contribute all our work back to the gem maintainer but we needed to carefully group related commits, and send meaningful pull requests

So, after forking the gem in question and a cheeky bit of diffing we knew the all the differences between our vendorised gem and that of the fork.

diff -uNrw . our/app/vendor/gems/gem_name/ | less

We created a temp branch and applied all the diffs by simply copying over the entire gem recursively.

git checkout -b temp
cp -r our/rails_app/vendor/gems/gem_name/ .

We ran all the tests in the gem, and gladly they passed, so we removed our vendor gem directory from our application - using a symbolic link instead pointing at the gem fork. Then we ran all our tests in our application and they passed too - which is great.

So at this point, the gem was still in the vendor/gems directory, however this pointed to a fork of the gem where many changes had been made.

The next step was to commit or interactively add hunks of code from all the changes in the temp branch.

git commit -v file
git add -i file

This created neatly separated commits of all changes in our temp branch.

Then we checked out the master branch and compared the temp branch to the HEAD revision. This showed us clearly the differences between the two branches.

git checkout master
git diff HEAD..temp

So, we’ve got all our commits on a temp branch and these needed to be selectively added to new branches, allowing us to logically aggregate changes, and issue pull requests to the gem’s maintainer.

So - first up create a new branch and then we started to cherry pick.

git checkout master
git checkout -b new_set_of_commits_for_pull_request

With a list of SHAs generated from all commits on the temp branch we could tell what we needed to cherry pick.

git log --oneline temp

So applying these commits to a branch is easy - giving us a branch with a specified set of commits on it.

git cherry-pick 42f169a 9068c89 750684e d5a7ac8

Then we push this branch, and follow github’s amazing pull request procedure.

git push origin new_set_of_commits_for_pull_request

Once our changes were accepted (phew!) we created a remote pointing to the gem repo, and fetched the contents. Then we tracked all upstream changes to this gem allowing us to merge back all upstream changes into our local master branch - using an intermediate “integrate” branch to test that all merges were fast-forwards.

git remote add gem_name <insert repo>
git fetch gem_name
git branch --track gem_name gem_name/master

Git is such a powerful SCM - it’ll take me a while to get my head around the possibilities. For me, to write the above down is really important - it helps me to remember for next time!