Sample Rails app: multi-OpenIDs per user

Posted by Dr Nic on July 26, 2007

Last time, on “Dr Nic loves OpenID”…

Dr Nic had watched a video by Simon Willison glorifying the delights of OpenID to some Googlers.

Dr Nic decided that each User might like to link multiple OpenIDs to their application account.

Of course, if a user wanted multiple accounts then they could use their different OpenIDs to do that too.

All Dr Nic had to do now, was write some code…

OpenId logo

In the mean-whilst…

Later that very same day, Dr Nic reads a tutorial by Joseph Smarr at Plaxo - A Recipe for OpenID-Enabling Your Site. He is joyed that Joseph had said the same thing:

It’s a many-to-one relationship (each user can have multiple OpenIDs attached to their account,
but a given OpenID can only be claimed by a single user)

“Champion!” Exclaims Dr Nic in the seclusion of his underground lair 300m isolated tower kitchen.

This article also lays out an implementation plan. It is in excruciating detail, including table schemas, stylesheet snippets and form suggestions.

Dr Nic rubbed his hands together and gets busy…

[/end of 3rd person]

Sample app

As a developer, the only reason you wouldn’t want to support “multiple OpenIDs per User” is because it is a PITA to implement, in that its an administrative bonus feature for your app. Its not really adding any real uber value.

So I followed his instructions - more or less - an have created a sample app. The README includes a demonstration of the app. You can also download it as a ZIP or TAR bundle.

User account with multiple OpenIDs

Now you can add multiple OpenIDs per user and be lazy at the same time.

The app builds on top of Ben Curtis’ OpenID sample app, and uses a variation of Ryan Bates’ Railscasts theme, because its sexy.

NOTE: The code is built for Rails Edge, but the two bundles do not include rails edge, so you need to rake rails:freeze:edge after unpacking it (all this and more in the README). If you want to work with Rails 1.2.3 gems, then.. um, I didn’t write a list of changes, but it might still work, I haven’t tested it. Ben’s original app was built for 1.2.3, so at its very heart it should still work. Perhaps.

Deviations from the Joesph’s tutorial

I want my users to be able to throw their OpenID around on any old page where they see an OpenID field.

That is, allow users to login or register with OpenID from either the “login” form or the “registration/signup” form. Within Rails, these are traditionally two separate controllers (sessions and users respectively).

So, I extracted out this functionality in to a module (helper user_openids_helper) and its used by the two controllers.

How’s it work?

Same way that Ben’s sample app worked, more or less, so read his article a couple times, then read through the source code for the multi-OpenID sample app.

I guess a couple tourist highlights might be:

  • app/helpers/user_openids_helper.rb - the extract fancy code
  • config/routes.rb - we need more routes to support alternate callbacks from OpenID providers
  • sessions_controller.rb and users_controller.rb - they are emptier and cleaner now
  • test\functionals\sessions_controller.rb - at the bottom are some test cases for the login/registration features of user_openids_helper

So, grab your Code Camera…

Auto-completer for my blog comments

Posted by Dr Nic on June 30, 2007

It took 4 hours to return from the town of Strängnäs to Stockholm via train. Normally, its 40 minutes. The train never turned up. 4 * 60 - 40 = 200 minutes of non-travel.

Stuck in a train station for 200 minutes I tackled a problem that has irked me for some time.

In my blog comments, when I reply to someone else’s comment I’ll normally use the syntax: “@chris - thanks for the kind feedback, I’ll send the money via paypal“.

And then Chris says “@Dr Nic - yeah, send it to the usual account; the tax man will never find it“.

And then I say “@chris - no problem“.

Its not that “@chris” is difficult to spell, but I’ve used IRC and Gmail long enough that I like auto-completion. The absence of auto-completion in Skype irks me too.

What I really want is:

“@c” + TAB expands to “@chris - “

So that’s what I now have. And its neat.

Demo of auto-completer

Unfortunately, the first person to leave a comment below won’t have anyone to reply to, and so theoretically wouldn’t get to experience the joys of the feature, that is so awesome and sexy that my very own wife called:

“That’s nice dear, now help with dinner”.

So, by default “Nic”, “Dr Nic”, “drnic” are available. The 2nd commenter will get these + the name of the 1st commenter. The 3rd commenter will get… hehe, you thought I was going to type that out.

Here’s what you’ll see when you save your comments:

Testing Autocompleter

Public release

Its currently implemented for Wordpress, using Prototype/Scriptaculous. Well it works on my Wordpress theme. It figures out the auto-completion list from the HTML, not from the blog/forum server.

I’m not sure how different everyone’s HTML is for their comment blocks. That makes it tricky to release the conde at the moment for general consumption. You might be an HTML guru and make it work, but your sister isn’t.

So, can you please comment below (wink wink) with the name of your blog software, and include your blog url in the URL field. I might be asking for grief here, but I’ll scope out everyone’s blog comment HTML and see how different/similar they are.

BUT!…

If someone in the comments has already mentioned your blog software (Wordpress, Mephisto, etc) then you MUST reply to their comment. If you are first to comment with your blog software, then use “@Dr Nic” instead.

This is life-or-death critical as it gives you an excuse to play with the auto-completer thingy. Lives are at stake here people!

Don’t have a blog, but want to comment anyway to play with it in all its snazziness? Go for gold.

Railsconf - the sessions I’ll be attending

Posted by Dr Nic on April 26, 2007


http://myconfplan.com/conferences/RailsConf2007/users/drnic

Dr Nic's conference selections

This is a new site called MyConfPlan, built using Hobo, on Rails. Hobo is awesome, but more on that another day.

MyConfPlan came about from the following thought sequence:

  1. Stupid Railsconf schedule is too hard to read
  2. Needs to be a table, yeah I should do that [Meanwhile, elsewhere on the InterWeb, another man tackles this problem too]
  3. Oooh, it would be cool to click on the sessions and select them
  4. And then I could show them off [as above]
  5. And other people could do that too
  6. And I’ll make MILLIONS OF DOLLARS!!!!

Going to RailsConf Europe? JavaOne? the local FooBarCamp or Unconference? You could use MyConfPlan to setup the schedules. If you’re organising an Unconf, soon you’ll be able to tick a box and all attendees can add/edit sessions. Neat indeed!

Going to RailsConf?

Use the comments below if you want to discuss Railsconf schedule or future MyConfPlan features. Or to adorn me with non-specific praise.

Not Going to RailsConf?

Be a devil - pretend to go to the conference. Click some buttons, select some sessions. You can cancel your attendance later.

Aliases to the latest branch folder you’re working on

Posted by Dr Nic on April 16, 2007

For my Rails and RubyGem projects I’ll run multiple branches plus a trunk. The trunk represents the live, public/production code, and the branches represent new features being developed [1]. So if I’m working on a branch for a feature, and I open a new terminal shell, THAT’s the working folder I want to go to, not the trunk, nor any other branch. Just that one.

Even for small gems and projects where I have no branches, just a trunk, I want quick ways to get to the working folder. [2] You know, the branch I was just working on two seconds ago.

So, if I have a project (rails app or gem) called wizzo, and I’ve checked out the trunk and some branches into a common folder:

  • /path/to/wizzo —trunk
  • /path/to/wizzo_feature1 —branch for feature1 (Last touched)
  • /path/to/wizzo_feature2 —branch for feature2

OR, if I checkout the trunk and all branches into one folder:

  • /path/to/wizzo/trunk —trunk
  • /path/to/wizzo/branches/feature1 —branch for feature1 (Last touched)
  • /path/to/wizzo/branches/feature2 —branch for feature2

I want an alias wizzo to take me to /path/to/wizzo/branches/feature1 if that is where I’m working at the moemnt.

If you want this too, do the following:

$ sudo gem install latest_branch

And for each folder that contains your projects, add the following to your .profile:

alias_all_projects --path=~/Documents/rails_apps/
. ~/.project_aliases

And this will generate many wonderful, dynamic aliases for you.

See the latest_branch website for the ins-and-outs, and its helper app latest_branch.

[1] At work - a non-ruby/rails, billing system on CVS - we do it differently. The trunk is for development of all new features, and production releases are tagged, and also branched for critical fixes. But the above code would be useful for both situations.

[2] Lucky zsh shell users can do fancy stuff like map folders to ~proj folders, and use autocomplete, such that cd ~p/t might go to ../proj/trunk. Neat.

Magic Multi-Connections: A “facility in Rails to talk to more than one database at a time”

Posted by Dr Nic on April 12, 2007

At this point in time there’s no facility in Rails to talk to more than one database at a time.

Alex Payne

I possibly have such a facility. Perhaps it will help, and I will get some DHH-love and perhaps a free Twitter account for my troubles. Or perhaps a t-shirt.

As a bonus, the solution even includes decent Ruby-fu syntax. So, if you’re just here for the view:

class PeopleController < ApplicationController
  def index
    @people = conn::Person.find(:all)
  end
end

That code just there solves all our problems. It will invoke Person.find(:all) on a random database connection to (assumably) a clone database. Awesomeness I think. I hope it helps Twitter and all the Twit-sers (or whatever you call a user of Twitter).

This solution comes from the magic_multi_connections gem.

What is going on here?

I think a tutorial is the best way to demonstrate what is happening here. So, let’s create a rails app and mix in the magic_multi_connections gem.

First, get the gem. Second, create a rails app:

$ sudo gem install magic_multi_connections
$ rails multi -d sqlite3

Now edit the config/database.yml file to create some more databases:

development:
  adapter: sqlite3
  database: db/development.sqlite3
  timeout: 5000

development_clone1:
  adapter: sqlite3
  database: db/development_clone1.sqlite3
  timeout: 5000

development_clone2:
  adapter: sqlite3
  database: db/development_clone2.sqlite3
  timeout: 5000

But please pretend these are uber-MySQL clusters or whatever.

Think of :development as the read-write connection, and the :development_cloneN connections are for read-only access.

At the bottom of your environment.rb file, add the following:

require 'magic_multi_connections'
connection_names = ActiveRecord::Base.configurations.keys.select do |name|
  name =~ /^#{ENV['RAILS_ENV']}_clone/
end
@@connection_pool = connection_names.map do |connection_name|
  Object.class_eval <<-EOS
    module #{connection_name.camelize}
      establish_connection :#{connection_name}
    end
  EOS
  connection_name.camelize.constantize
end

Let’s test what this gives us in the console:

$ ruby script/console
>> @@connection_pool
=> [DevelopmentClone1, DevelopmentClone2]
>> DevelopmentClone1.class
=> Module
>> DevelopmentClone1.connection_spec
=> :development_clone1

Our new modules will act as connections. One module per connection. The code above gives them names to match the connection names, but its really irrelevant what they are called, thanks to the mysterious conn method.

So, go create some models and some data. I’ll use Person as the class here.

To setup the schemas in our clone databases, we’ll use rake db:migrate. To do this:

$ cp config/environments/development.rb config/environments/development_clone1.rb
$ cp config/environments/development.rb config/environments/development_clone2.rb
$ rake db:migrate RAILS_ENV=development
$ rake db:migrate RAILS_ENV=development_clone1
$ rake db:migrate RAILS_ENV=development_clone2

To differentiate the databases in our example, assume there are two Person records in the :development database, and none in the two clones. Of course, in real-life, they are clones. You’d have a replicate mechanism in there somewhere.

Now, we can access our normal Rails modules through our connection modules. Magically of course.

>> ActiveRecord::Base.active_connections.keys
=> []
>> Person.count
=> 2
>> ActiveRecord::Base.active_connections.keys
=> ["ActiveRecord::Base"]
>> DevelopmentClone1::Person.count
=> 0
>> ActiveRecord::Base.active_connections.keys
=> ["ActiveRecord::Base", "DevelopmentClone1::Person"]

Wowzers. Person and DevelopmentClone1::Person classes?

But note - Person.count => 2 and DevelopmentClone1::Person.count => 0 - they are accessing different databases. The same class definition Person is being used for multiple database connections. We never defined more Person classes. Just the standard default one in app/models/person.rb.

The active_connections result shows that DevelopmentClone1::Person has its own connection. Yet you never had to manually call DevelopmentClone1::Person.establish_connection :development_clone1 - it was called automatically when the class is created.

Of course, DevelopmentClone2::Person is automatically connected to :development_clone2, and so on.

Behind the scenes

Let’s look at our generated classes:

$ ruby script/console
>> DevelopmentClone1::Person
=> DevelopmentClone1::Person
>> Person
=> Person
>> DevelopmentClone1::Person.superclass
=> Person

That is, there is a DevelopmentClone1::Person class, automagically generated for you, which is a subclass of Person, so it has all its behaviour etc.

Dynamic connection pools within Rails controllers

The magic of the conn method will now be revealed:

$ ruby script/console
>> def conn
>>   @@connection_pool[rand(@@connection_pool.size)]
>> end
>> conn::Person.name
=> "DevelopmentClone2::Person"
>> conn::Person.name
=> "DevelopmentClone1::Person"

The conn method randomly returns one of the connection modules. Subsequently, conn::Person returns a Person class that is connected to a random clone database. Booya. Free Twitter swag coming my way.

Place the conn method in the ApplicationController class, and you can get dynamic connection pooling within Rails actions as needed, as in the following example (from the top of the article):

class PeopleController < ApplicationController
  def index
    @people = conn::Person.find(:all)
  end
end

Decent Ruby-fu, I think. Certainly better than manually calling establish_connection on model classes before each call (or in a before_filter call, I guess).

This is just a concept

I know this tutorial above works. But that might be the extent of what I know. Let me know if this has any quirks (especially if you solve them), or if this is a stupid idea for implementing connection pooling with nice Ruby syntax.

Hope it helps.