Twitter Gem Reborn with Fewer Features and 100% More OAuth

The Twitter gem is kind of my baby. It was pretty much the first gem I created and as such I care for it a little more. When I originally created it, I knew nothing. I sucked. Now over time, I suck less and Twitter’s addition of OAuth seemed like the perfect time to completely rewrite it. Yay for rewrites!

The gem is now leaner, meaner and works swell with OAuth. I would imagine as I use OAuth a bit more, I’ll figure out some ways to make it even easier in the twitter gem but it works for now.

The Cuts

One of the biggest headaches and coolest things about the Twitter gem was the command line interface. It allowed for multiple accounts and was SQLite database backed. That was cool and all but it is ridiculous to have main and active record as dependencies for someone who just wanted the API wrapping aspects of the gem.

Knowing this, I have finally killed the CLI. Fear not, CLI fans, it will make a triumphant return, but that return will be in a separate gem that merely relies on the Twitter gem. The Twitter gem itself will be only a wrapper for the REST and Search APIs that Twitter provides. No more extra cruft.

OAuth

As I said, the main reason for the rewrite was that Twitter now supports OAuth. I thought about it long and hard and decided to not continue support for HTTP Authentication. Twitter has not discontinued support for HTTP Authentication, but they will in the future and I didn’t want to add a bunch of hours to releasing the updated gem simply to support something that is going to be deprecated.

So how does one use the OAuth stuff in the Twitter gem? First off, familiarize yourself with OAuth. Then, you can check out the example below pulled straight from the examples directory included in the gem (or over here on github).

require File.join(File.dirname(__FILE__), '..', 'lib', 'twitter') require File.join(File.dirname(__FILE__), 'helpers', 'config_store') require 'pp' config = ConfigStore.new("#{ENV['HOME']}/.twitter") oauth = Twitter::OAuth.new(config['token'], config['secret']) if config['atoken'] && config['asecret'] oauth.authorize_from_access(config['atoken'], config['asecret']) twitter = Twitter::Base.new(oauth) pp twitter.friends_timeline elsif config['rtoken'] && config['rsecret'] oauth.authorize_from_request(config['rtoken'], config['rsecret']) twitter = Twitter::Base.new(oauth) pp twitter.friends_timeline config.update({ 'atoken' => oauth.access_token.token, 'asecret' => oauth.access_token.secret, }).delete('rtoken', 'rsecret') else config.update({ 'rtoken' => oauth.request_token.token, 'rsecret' => oauth.request_token.secret, }) # authorize in browser %x(open #{oauth.request_token.authorize_url}) end

The ConfigStore is a simple class that allows me to save the request and access token and secrets as I’m completing the handshakes between the script and Twitter.

As you can see, it is pretty simple. Get a request token, then use that to create an access token. From there, you can reuse the access token to authorize future requests.

An Example App

Now one might think, based on that previous example, “Thanks so much John! I really appreciate you getting the ball rolling for me with a simple example.” Friends do I have news for you. I didn’t stop there. I whipped together a little sample app, that I have been using for a few days now. Eventually, it will become a full Twitter client, but for now it is pretty basic and dirty.

It uses Clearance for authentication for authentication and automatically sends you to Twitter to authorize after firing it up (if you haven’t previously). I used Rails rescue_from to automatically send you to Twitter if any of the authorization stuff gets messed up at a later point to.

rescue_from Twitter::Unauthorized, :with => :twitter_unauthorized private def twitter_unauthorized(exception) redirect_to new_authorization_url end

Anytime the Twitter gem throws the Twitter::Unauthorized exception, you are redirected to new_authorization_url, which is powered by the AuthorizationsController, which looks like this.

class AuthorizationsController < ApplicationController def new oauth = current_user.oauth session['rtoken'] = oauth.request_token.token session['rsecret'] = oauth.request_token.secret redirect_to oauth.request_token.authorize_url end def show oauth = current_user.oauth oauth.authorize_from_request(session['rtoken'], session['rsecret']) session['rtoken'] = nil session['rsecret'] = nil current_user.update_attributes({ :atoken => oauth.access_token.token, :asecret => oauth.access_token.secret, }) redirect_to root_path end end

The new action stores the request token and secret and then redirects you to Twitter to authorize the app. Over at Twitter, I have registered an application and set the redirect url to go to /authorization in the app, which as you can see above, obtains the access token and secret and stores them with the current user.

You’ll probably notice that I am calling current_user.oauth and current_user.client throughout the app. These are helper methods I added to the user model to shorten up what was happening in the controller.

class User < ActiveRecord::Base include Clearance::App::Models::User attr_accessible :atoken, :asecret def authorized? !atoken.blank? && !asecret.blank? end def oauth @oauth ||= Twitter::OAuth.new(ConsumerConfig['token'], ConsumerConfig['secret']) end def client @client ||= begin oauth.authorize_from_access(atoken, asecret) Twitter::Base.new(oauth) end end end

I could shorten up the controllers even more (and will at some point) by delegating several methods on user to user.client. I’ll post more on that later when I get around to it. Also, at some point, I’m going to remove the atoken and asecret from the user and allow support for multiple Twitter accounts for one user in the app. This should be pretty easy, but I wanted to get something working first before I complicated things.

Conclusion

From a user standpoint, OAuth is really nice. I love not having to put in my username and password. From a developer standpoint, OAuth is also pretty nice. Basically, you register your application with Twitter, provide the consumer token and key in a config file and this example app is ready to go.

As with any rewrite there are bound to be bugs. I have fixed one already that someone caught. If you catch any weird bugs using the rewritten Twitter gem, there is group and ticket information on the Twitter gem website.