Saturday, September 11, 2010

Adventures in Ruby Land: a tutorial on ruby

  • Updated: 15-Sep-2010; section on modules and constants
escher like hands drawing hands
A Penrose Impossible Triangle around which circle the 3 main objects in ruby land
This is a tutorial on the ruby programming language and how it's structured which I'll refer to as ruby land. Ruby is a very dynamic programming language with a particular emphasis on object oriented programming but with some nice functional aspects.
This article might be useful to anyone who wants to get a bit of a roadmap on how ruby is structured. It could also be wrong. But if it's wrong, I'd like to think that it might still help to provide a framework or starting point from which to get the right picture.
You should be familiar with things like object oriented programming, basic ruby programming and irb (ruby's repl) which is very useful for trying things out as you think and experiment.

In the beginning... there was Object

The first thing you need to know in ruby is that everything is an object. Even classes are objects.
So what is an object? An object is an instance of a class. Objects don't contain methods - these are defined by the class of the object1. All an object does is keep track of the particular state of a particular instance of that class 2. Consequences of this will be discussed further on.
1 Ok, we could say that classes "have" or probably better, "define" instance methods. But these methods are intended for instances of the class. However an object that is a class has a class in turn which defines instance methods for it.
2 state is stored in instance variables in ruby; these are variables that start with a single `@` in their name.

Class and Superclass

We said everything in ruby is an object and this can be seen by inspecting the class method. Every object has one, even the classes because every object is an instance of some class when you get down to it.
Let's create a class:
class Foo
end
Now let's create an object:
foo = Foo.new
Now we can look at the class of foo
foo.class # => Foo
This tells us that foo's class is Foo. foo gets its "instance methods" from Foo. But we can also look at the class of Foo
Foo.class # => Class
They're both objects and objects must have a class that they belong to.
Objects that have a class of Class are clearly special objects because they are classes and they can be used to instantiate new instances (foo = Foo.new).
  • We'll call any object with a class of Class, a class-like object.
  • We'll call any other object as an instance or object instance.
  • We'll refine this a little when we get to modules.

Superclass

Another thing that makes class-like objects stick out from their peers is the fact that they have a superclass. This is no surprise. If you're a class, you're probably a subclass of some other class. That's how classes work. Instances of a class may have access not only to the methods of their class, but also any superclasses of that class.
Back to our example of Foo
  Foo.superclass # => Object
But foo, being just a lowly object instance, doesn't:
  foo.superclass # => Error!
We could make a new class SubFoo and make it a subclass of Foo like this:
  class SubFoo < Foo
  end
Now we have:
  SubFoo.superclass # => Foo
  SubFoo.superclass.superclass # => Object
In the land of class-like objects and by extension, the instances that are built from them, all roads eventually lead back to Object.

Instance Land

Here is Instance Land:
ruby instance land
This is the land of object instances. foo is an instance of Foo (foo = Foo.new); bar is an instance of Bar. obj is an instance of Object (obj = Object.new).
A lot of the basic work gets done in Instance Land because ruby encourages you to define and then instantiate classes like Foo in order to create object instances that store specific state information that can be readily modified as your program goes about solving its problems.

Class Land

Now let's add in Class Land. Like the men in Plato's cave staring at the shadows cast on the wall by the fire behind them, the object instances in Instance Land look up to Class Land, the land of Platonic ideas. This is where we define the idea of a Dog, or an Animal, or in our case a Foo or a Bar. Once those ideas have been fashioned we can then instantiate them to create object instances.
ruby class land
We draw the dotted lines from object instances to the classes in Class Land which define them. These dotted lines represent the class relationship that stamps a particular object instance as being an instance of a certain class.
In Class Land we can also trace out the relationships that classes maintain between themselves. The thick black lines with white arrow tips are the superclass lines that tell us that a certain class is a subclass of another.
SubFoo is a subclass of Foo. We write it like this:
  class SubFoo < Foo
  end
  SubFoo.superclass # => Foo
  Foo.superclass # => Object
But, this being ruby, there is yet another level. A level that the class-like objects themselves look up to. Welcome to...

Strange Land

Strange Land is strange. We have come to the axis of the ruby world; this is the backbone on which ruby is built. To stretch the Plato cave allegory just a little bit too far, it is as if we climbed out of the cave, past the fire and up into the daylight to see the real world. There are essentially three main protagonists: Class which is a subclass of Module, Module, which is a subclass of Object and of course Object itself.
ruby strange land
The class-like objects in Class Land look up to these objects in Strange Land the way object instances in Instance Land look up to classes in Class Land. (Well, excluding Object which we include in Class Land to keep the diagram slightly this side of sane.)
The denizens of Strange Land are class-like objects. Their class is Class. Even Class has a class of Class. And as just noted they form a class hierarchy that can be explored using superclass.
What makes things slightly strange is that Object is at the top of the tree but its class is one of its subclasses Class.
escher like hands drawing hands
Object has class of Class; Class has superclass of Object. A small homage to M.C Escher's "Drawing Hands".
Object itself is a funny beast. As noted earlier, all roads lead back to Object because, well, everything is an object.
self is always the same in Object
  class Object
    def self.self1
      self
    end
    def self2
      self
    end
  end
  Object.self1==Object.self2  
Object allows you to extend it with methods which will then become available to all other objects at all levels of ruby land.
  class Object
    def everywhere
      "hi I'm #{self}"
    end
  end
Whilst Object defines the notion of class, Class gives it the ability to instantiate objects and the idea of class hierarchies (superclass).

Module Land: Modules and instance methods

We need to briefly step down from Strange Land to look at modules.
Module Land is a strange little adjunct to Class Land. Members of Module Land have a class of Module, not Class. They are instances of a thing that precedes the notion of Class itself. So the denizens of Module Land can't be instantiated like Foo or Bar. This is because in Strange Land, Module sits in-between Object and Class and appears to represent a primordial Class; a thing that is not able to confer the ability of instantiation upon its instances, but which encapsulates the idea of instance methods.
Unlike Object where self is always the same, self is most definitely not always the same in an instance of Module. This is because Module introduces the idea of instance methods, methods that are defined in a class-like object but which are used only by instances of that class-like object.
  m = Module.new do
    # Singleton method
    def self.self1
      self
    end
    # Instance method
    def self2
      self
    end
    self
  end
  m == m.self1       # => true
  # At this point is already clear self1 and self2
  # are not the same because self2 does not apply to m.
  # Instead we could try to add self2 to some other object:
  o = Object.new.extend(m)
  o.self2 == m.self1 # => false
In the above, self1 is a singleton method which can be called on m - more on singleton methods later. self2 cannot be called on m because self2 is an instance method. The only we can use this is to extend another object with it or include it into a class-like object at which point self2 will return the self of that object.
So modules (the instances of Module) can't create instances of themselves but they can provide instance methods to be mixed in to other classes or used to extend other objects.
Classes (instances of Class) inherit the idea of instance methods from modules but go one step further, allowing themselves to be instantiated so that these instance methods can be used.

Modules and Constants

Ruby constants are an interesting construct. Use a variable name that starts with a capital letter and you have a constant, a very different thing to a non-capitalised variable. Constants can be seen by the rest of your program.
You can create constants as easy as this
Foo=1
You can't nest constants in constants:
Foo::Bar=2  # Error
makes no sense. This is where modules come in again. You can set constants inside a module and access them accordingly. Instances of Module have instance methods for handling constants contained by a module.
If our module is Bar, we can define a constant inside it:
module Bar
  Foo=1
end
Bar::Foo # => 1
The module that houses the constant doesn't have to be referenced by a constant:
m = Bar
m::Foo # => 1
Here m is a local variable.
You can see the instance methods for handling constants that Module defines for its instances like this:
irb(main):013:0> Module.instance_methods(false).sort.grep(/const/)
=> [:const_defined?, :const_get, :const_missing, :const_set, :constants]
For example:
irb(main):019:0> m.constants
=> [:Foo]
Modules and constants work hand in hand to allow you to house and namespace your code. Because classes are instances of a subclass of Module, they inherit the same capabilities.

Where do class-like objects get their methods from?

Modules and classes define instance methods which ultimately get used by instances of classes. So where do classes and modules themselves as objects get their instance methods from?
Take for example the include method. This is a private instance method of Module:
    irb(main):010:0> Module.private_instance_methods(false).sort
    => [:alias_method, :append_features, :attr, :attr_accessor,
    :attr_reader, :attr_writer, :define_method, :extend_object,
    :extended, :include, :included, :initialize, :initialize_copy,
    :method_added, :method_removed, :method_undefined,
    :module_function, :private, :protected, :public,
    :remove_const, :remove_method, :undef_method]
What does that mean?
It means that instances of Module (ie modules) will have an include method. Because Class is a subclass of Module, instances of Class (ie classes in Class Land) will also have include.
But because Module has a class of Class, it too will have include as a method, inherited, oddly enough, from itself since Class is a subclass of Module1. :)
Perhaps another name for Strange Land might be "Metaclass Land".
1Note, the underlying implementation of ruby (in C) may be somewhat more straightforward (I don't actually know). We are just looking at the "logic" that ruby presents to us as ruby programmers.

Singleton Methods

It gets stranger.
Recall that we said that everything is an object and that objects don't contain their own methods1 but derive their methods from the class that they belong to.
1 Class-like objects "contain" instance methods, but these are for their instances.
Well, if you've programmed for any time in ruby you might be aware that you can add singleton methods to objects:
  foo = Foo.new
  foo2 = Foo.new
  def foo.a
    'a'
  end
  foo.a # => 'a'
  foo2.a # => Error!
foo and foo2 are both instances of Foo and have the instance methods that are defined by Foo. But foo now has a method that foo2 does not.
We can do the same thing with class-like objects of course:
  def Foo.a
    'a'
  end
  Foo.a # => 'a'
Often, singleton methods for classes are defined like this:
  class Foo
    def self.a
      'a'
    end
  end
Singleton methods for class are like class methods in other programming languages like java.

Extend and include

As a small digression:
Yet another way to add singleton methods to an object is extend. extend is a method defined by ruby's Kernel module which is mixed in to Object and so is available to any other object in ruby land. extend takes the instance methods defined by a module and adds them as singleton methods to the object being extended.
Compare this to include which is a private instance method defined by Module which injects instance methods into a class-like object.
So where do these mysterious singleton methods belong if objects always derive their "instance methods" from a class?

Behold, Shadow Land!

Well, the shadowy answer to this conundrum is that they get it from a class, just not the usual class that we've been talking about up till now.

Virtual Classes

In fact, this is going to come as a shock, but every object in ruby land has two classes. It has a class - the bright shiny platonic things in Class Land we discussed earlier. But also something called a virtual class (some people refer to it as an eigenclass), a shadowy denizen that resides in a kind of bottomless underworld of usually unnamed and often unseen classes.
shadow land
The dotted lines are `class`; the white arrow lines are `superclass`.
One way to think about virtual classes is that every object in ruby, class-like or instance or whatever, has its own special, unique class that shadows it and can be called upon to stash instance methods that are unique to that object. Such methods are best referred to as singleton methods.

Getting the virtual class

Getting the virtual class is a tricky business. (It may have gotten less tricky in recent times - I don't know - I'm just referring to the state of play with the now bewhiskered ruby 1.8.6 as an example).
One way to get it is by using ruby's class syntax in a form that is both totally ungoogleable and rather unintuitve. We can open one up like this:
  class << Foo
    ... do something with Foo's eigenclass ...
  end
The self inside this class-statement is our virtual class.
We can make life easy for ourselves here and stash a method directly in Object to get the above self
  class Object
    def eigenclass
      class << self; self; end
    end
  end
Adding a method to Object will make it accessible to all other objects in ruby land; self in Object is always the same. This technique was mentioned some years ago on the ruby mailing list.
Back to our example of def Foo.a, the singleton method we defined above. We note that:
  Foo.instance_methods(false).sort
does not show a; but
  Foo.eigenclass.instance_methods(false).sort
does.
You'll note that Shadow Land has several layers in the above diagram. In fact those layers just keep going down:
  irb(main):015:0> Object.class
  => Class
  irb(main):016:0> Object.eigenclass
  => #<Class:Object>
  irb(main):017:0> Object.eigenclass.eigenclass
  => #<Class:#<Class:Object>>
  irb(main):018:0> Object.eigenclass.eigenclass.eigenclass
  => #<Class:#<Class:#<Class:Object>>>
  ...
And so it goes...

Practicalities

So how does mapping out ruby land help in the real world?
Well, if we look back at the above map of ruby land we can see that it is segmented into the different "lands" and that this separation revolves around the act of instantiation.
We can see, for instance, that
  Class.new
will give use an anonymous Class-like object in class land. We can further see that
  Class.new.new
will give us an anonymous instance of an anonymous class.
We can also create anonymous modules:
  m = Module.new
and it should be clear that modules can't be instantiated any further.
We can see as a result that:
  class Foo
  end
is just a nice way to assign a new class to the constant Foo
  Foo = Class.new
Similarly for modules:
  module M
  end
becomes
  M = Module.new
We're really only a hop, skip and a jump away from meta programming - building classes and object oriented structures on the fly.
We can also see that to understand ruby to any deep level including metaprogramming we must first inspect and appreciate the 3 central objects that form the backbone of ruby land: Object, Module and Class and the roles they play.

Final Thoughts...

Personally, I think one can go a little too far by viewing the world exclusively in an object oriented way. This is a static world where things like to be isolated and named and allotted associated behaviours and where potentially they can be specialisations (subclass) of more general things. Ruby sits in an interesting space with its highly dynamic approach to this static world.

Final notes

A word on variables and scope

There are at least 5 different types of variables. Ruby's scoping rules vary depending on the type.
  • Globals
    • These start with a $
    • Ruby has many in-built globals
    • Globals are accessible everywhere
  • Constants
    • Any variable name that starts with a capital becomes a constant
    • like globals, constants can be seen anywhere
    • however, constants can be nested
    • if you define a constant within the context of a module or class, that constant will be nested within that context
          class Foo
            Bar='bar'
          end
          Foo::Bar # => 'bar'
      
    • constants are usually camel-cased where as instance, class and local variables are usually underscored
    • constants are often used to store modules or classes
  • Instance variables
    • Any variable that starts with @ is an instance variable.
    • Every object will have a bunch of these.
    • One gotcha for ruby novices is to assume that @inst1 is an instance variable of an instance of Foo (eg Foo.new).
        class Foo
          attr_reader :inst1
          @inst1='inst1'
        end
      
      It is not.
        Foo.new.inst1 # => nil
      
      We can show that it is an instance variable of the object Foo by defining an instance method to access it in Foo's virtual class:
        class << Foo
          attr_reader :inst1
        end
        Foo.inst1 # => 'inst1'
      
  • Class variables
    • Any variable that starts with @@ is an class variable.
    • class variables allow instances of a class to access state that is associated with that class; these variables are per-class whereas instance variables are per-instance.
  • local variables
    • anything that isn't prefixed by @ or @@ or $ or a capital letter is a local variable (or possibly the name of a method available within the context you are working in)

Ruby 1.9

Ruby 1.9 introduced a superclass to Object called BasicObject. I've omitted it here.

Tuesday, September 7, 2010

Split and join (in Javascript)

This article...

I want to take a quick look at splitting and joining text using javascript.

Splitting

Suppose you want to split some text. A language like javascript (and many other languages besides) makes this very easy:

  ',,a,,,,b,,c,,'.split(/,/) // case (I)
  => ["", "", "a", "", "", "", "b", "", "", ]

Or

  ',,a,,,,b,,'.split(/,+/) // case (II)
  => ["", "a", "b", "", ]

You can recreate the string for (I) using an in-built join function:

  ',,a,,,,b,,'.split(/,/).join(',')
  => ',,a,,,,b,,'

Case (II) can't be put back the way it was because we do not know for any given joining point the size of the joining item since /,+/ matches a variable number of characters (commas in this case).

Joining for case (I)

Sometimes, you want to join a split as in case (I) but not back into a string. When I first tried to do this I ended up writing a horrifically complicated function.

Looking at this case again:

  ',,a,,,,b,,c,,'.split(/,/) // case (I)
  => ["", "", "a", "", "", "", "b", "", c, "", "", ]

The thing to remember is that "" represents the gaps between the commas in ',,a,,,,b,,c,,' including the gap before the very first comma and the gap after the very last comma. "a","b" etc are filled-in gaps. This is probably what is confusing about manually joining such a split array; because it's easy to fall into thinking that the ""-terms represent commas instead of the gaps.

Algorithm for manual joining

From an algorithmic point of view we want to map over the array produced in case (I) and process both the "" and non-"" terms.

The commas in the string may signify a point where we want to insert something. In my case, the strings I was splitting were text nodes from preformatted text (in pre-tags) that contained line feeds (\n or \r\n). I was tokenizing the text and wanted to preserve line feeds in the form of individual span tokens. So in this case the commas in case (I) would represent line feeds eg '\n\na\n\n\n\nb\n\nc\n\n' instead of ',,a,,,,b,,c,,'.

Going back to case (I), the terms (or gaps) are the best indication of where the commas are; if there are n commas, then there will be n+1 gaps (including filled in ones). Keeping this in mind the rules we could follow as we map over the array might be:

  • when we have a ""-term we insert comma
  • when we have a non-"" term we insert term followed by a comma
  • at the last position in the array don't insert a comma
    • if last position in the array is a "" then do nothing
    • if last position in the array is a filled-in gap, process it but don't insert comma

Functional approach

There are some nice ways to do this in javascript. Ecmascript 5 probably has mapping functions that might assist but here is a manual version that whilst not overly functional, facilitates a functional style when used (using the term 'functional' in a very loose sense):

  // Join elements that have been split by String.prototype.split(...).
  var join = function(arr,unsplit,process) {
      var i,l=arr.length;
      for(i=0;i<l;i++) {
          if(arr[i]!=='') process(arr[i],this);
          if(i!=l-1) unsplit(this);
      }
  }

Notes:

  • unsplit is a function that represents the "insert comma" operation
  • process is a function that represents the "insert term" operation which we apply to filled-in gaps like "a"
  • in addition, we pass this to both unsplit and process as this can faciliate sharing privileged information between unsplit and process; although this isn't necessary.

We could run join like this:

join(arr,f,g)

for some array arr and functions f and g.

But suppose we want to accumulate a result as join maps over arr or otherwise share privileged information between f and g, this is where this could be used:

var module1 = function() {
  var prog1 = function(text) {
    ...
    var someObj = {};
    ... initialize someObj ...
    var arr = text.split(...);
    join.call(someObj,arr,unsplit,process);
    ...
  }     
  var unsplit = function(obj) {
    ...
  }     
  var process = function(item,obj) {
    ...
  }     
}();

In the above we have a function prog1 inside a module that performs a split on some text. We invoke join using call passing someObj as the first argument; this becomes the this reference within join which in turn passes this to unsplit and process

Variations

We could skip using call/this and simply add an extra paramter to join to allow us to pass an object in.

Or we could also call unsplit and process. This removes the need to specify the obj parameter in these two functions:

  // Join elements that have been split by String.prototype.split(...).
  var join = function(arr,unsplit,process) {
      var i,l=arr.length;
      for(i=0;i<l;i++) {
          if(arr[i]!=='') process.call(this,arr[i]);
          if(i!=l-1) unsplit.call(this);
      }
  }
  var unsplit = function() {
    ... do something with 'this' ...
  }     
  var process = function(item) {
    ... do something with 'this' ...
  }     

We could also define unsplit and process within prog1 giving these functions privileged access to someObj. These functions would be generated every time prog1 is invoked. But there would be no need to mess about with an extra parameter or this.

Wednesday, September 1, 2010

Surviving the twitter OAuthcalypse on the commandline (using Ruby)

Surviving the twitter OAuthcalypse on the command line

In this article...

  • I try to cover how to use twitter apis from the (linux) commandline via OAuth using the ruby twitter gem

Warning:

  • I'm a very light user of web services and social media in general
  • I have little knowledge of OAuth other than a general appreciation of what it is trying to do

Quick background...

I woke up to the OAuthcalypse today.

Up till now I had been using twitter in a very innocent, low-cal kind of way from the commandline (via an ungodly combination of curl and bash/shell utilities) and also from emacs. Both methods mysteriously failed today leaving me with blank screens and cryptic error messages.

Whilst I should have probably given up at this point and embraced one of the popular twitter services, I ended up instead, wasting half a day wrestling with OAuth in a bid to get my twitter commandline working again.

Ruby twitter gem

I've given up for the moment using shell utilities to access twitter like before the OAuthcalypse, although this might be possible. Instead, I'm going to use ruby which for me is rapidly turning into the new perl.

There are probably numerous libraries in ruby for doing twitter but I chose John Nunemaker's twitter gem

  • I found it helpful to get the actual source which I git cloned
    • this turned out to be useful because the source includes a number of example files that are worth looking at
  • That being done, I installed the twitter gem in the normal way
      gem install twitter
    
    • This will load several other gems; in my case:
      • oauth-0.4.2
      • hashie-0.2.2
      • crack-0.1.6
      • httparty-0.5.2
      • yajl-ruby-0.7.7
      • twitter-0.9.8
  • At this point you should be able to do a require 'twitter' successfully

OAuth Terminology

Just to be clear, here are the main protagonists in an OAuth exchange:

  • service is an oauth enabled web service like twitter
  • user is a person that has an account with a service and who is using a consumer (or app) to access that service
  • consumer - consumes a service; a consumer may itself be some sort of service or a client application that the user is using; the consumer has to use oauth protocols to access the user's information in service
  • app is alternative name for a consumer; I use both interchangeably

OAuth 1.0a and "out of band" (oob) processing

I'm going to cover the OAuth 1.0a process as it pertains to twitter and as best I can understand it after one day of head pounding.

Note:

Back to OAuth:

OAuth requires 3 sets of tokens; each set consists of a token and a shared secret:

  • Consumer token / secret ctoken/csecret
    • this is a once-only token and shared secret that identifies the app (consumer)
    • you only need one for your app; so once you get it, you stash it somewhere where your app can load it
    • in the case of twitter you can set up access privileges when setting these up; for twitter this is whether the consumer will have read-only or read/write access
    • you can go here to arrange twitter to generate the ctoken/csecret pair for you
      • twitter will require you to give it some information such as the application name and a description
        • interestingly, you can't leave the description blank and twitter doesn't like you putting in an app name that has 'twitter' in it; I think you are also required to put in a url for the app
        • I never had to bother with this when I was using my old commandline app with http auth api so I am wondering if this is the only way to proceed now
  • Request token / secret rtoken/rsecret
    • this is a transient token/secret pair that appears to represent the act of requesting an atoken/asecret pair; once you've used it to request an atoken/asecret pair (and hopefully succeeded), you can dump it
    • you need to present a valid ctoken/csecret pair to twitter before you can get a rtoken/rsecret pair
    • to "activate" this rtoken/rsecret pair the user is required to authenticate with the service (in the case of twitter via a specially crafted login url)
      • the user will be asked to login and then specify whether the consumer that initiated the request token should be allowed to proceed (allow or deny)
        • note: it may also be possible here to specify authorization privileges but in the case of twitter, this was done during the consumer token phase above
      • the user authenticates successfully and clicks 'allow',
      • because we're using out of band (oob) processing, the service will display a pin number; the user needs to (manually) give this to the app (our commandline twitter-gem based app) in order for it to proceed
        • the pin number is part of the "out of band" process flow; since we're trying to access twitter from the command line, we are very much out of band
  • Access token / secret atoken/asecret
    • the app uses the pin and the associated rtoken/rsecret pair from the previous step, to authenticate itself with the service; all going well, the service should provide an atoken/asecret pair
    • Once the app/consumer has an atoken/asecret pair, it can access the user's data from the service; this pair is acting like a substitute username/password.
      • note that the app/consumer never gets to see the real username/password of the user's account for the service and that the access pair can be easily revoked or set to expire

Using ruby twitter gem

Setting up config

  • If you looked at the examples section of the source for the twitter gem, there is a helpers/ directory containing config_store.rb
    • This defines a small class called ConfigStore that can be configured to load information (such as stored tokens) from a yaml file
    • Here's an example yaml file
        --- 
        ctoken: random-string-of-characters
        csecret: random-string-of-characters
        atoken: random-string-of-characters
        asecret: random-string-of-characters
      
    • You'll need to set ctoken and csecret by visiting twitter to register your application.
    • You won't be able to set atoken or asecret; these will be stored by ConfigStore when you do a successful authentication so leave these out for now
    • I use a slightly modified version of config_store.rb which I copied from examples/ into my directory of choice

Managing an OAuth session

The first thing we've got to do is manage an OAuth session.

I've managed to boil it down to one of two routes:

  • if you don't have a valid atoken/asecret, the you'll need to do a "full login" which means requesting an rtoken/rsecret pair and then getting a pin out-of-band and feeding it back to our commandline app
  • if we have a valid atoken/asecret, then we can skip the above rigmarole and access the service directly since the atoken/asecret is acting like a temporary username/password.

I've encapsulated this behaviour in a TwitterSession class:


require 'twitter'
require File.join(File.dirname(__FILE__), 'config_store')
require 'pp'

# Handles OAuth authentication with twitter.
#
# @config must already contain a valid 'ctoken' and 'csecret'
# which you can get from twitter: http://twitter.com/oauth_clients/new

class TwitterSession

  attr_reader :oauth,:config

  def initialize config
    @config = ConfigStore.new(config)

    # Request rtoken/rsecret and login url from service:
    @oauth = Twitter::OAuth.new(@config['ctoken'], @config['csecret'])
    @config.update({ 'rtoken'  => @oauth.request_token.token,
                     'rsecret' => @oauth.request_token.secret, })
  end

  # Request new atoken.
  #
  # @config must already contain a valid 'ctoken' and 'csecret'
  # which you can get from twitter: http://twitter.com/oauth_clients/new
  #
  # You will need to do an out-of-band process which
  # will load a browser (lynx) to log the user into
  # twitter and which will provide a pin.

  def login

    # Get user to login and allow the consumer to proceed:
    #%x(firefox #{@oauth.request_token.authorize_url})
    system %{lynx #{@oauth.request_token.authorize_url}}

    STDOUT.print "> what was the PIN twitter provided you with? "
    pin = STDIN.gets.chomp

    @oauth.authorize_from_request(@oauth.request_token.token,
                                  @oauth.request_token.secret,
                                  pin)
    @config.update({ 'atoken'  => @oauth.access_token.token,
                     'asecret' => @oauth.access_token.secret,
                   }).delete('rtoken', 'rsecret')
  end

  # Login with existing atoken.

  def login_with_atoken
    if(@config['atoken'] && @config['asecret'])
      @oauth.authorize_from_access(@config['atoken'], @config['asecret'])
    else
      login
    end
  end

end


In the above file:

  • In addition to requiring the twitter gem, I also require my version of config_store.rb which is almost identical to the one in examples/
  • we initialize TwitterSession by giving it a name of the config store
    • I have multiple accounts which, for the moment, I'll manage in separate stores and which we will instantiate separate TwitterSession instances for
  • intialize makes an oauth call to get the request token/secret pair
  • login is the full login method
    • it uses ruby's system to call lynx which loads up the authentication/authorization page on twitter where we will get the pin; this will all get done in the same console;
    • we copy the pin to the clipboard
    • we then quit lynx
    • the procedure will then ask for the pin and read it from STDIN
    • login will then attempt to get atoken/asecret using the rtoken/rsecret pair and associated pin
  • login_with_atoken is the quick login method which will only work if you have an existing valid atoken/asecret in your config store
    • You'll be able to run this most of the time after doing a single login.

Getting your timeline

We take the above TwitterSession class and use it like so:


require File.join(File.dirname(__FILE__), 'session')
sess = TwitterSession.new("/home/danb/twitter2/config_store")

# Force full login if we specify -l on commandline.
if ARGV[0]=='-l'
  sess.login
else
  sess.login_with_atoken
end

client = Twitter::Base.new(sess.oauth)
pp client.user_timeline


In the above file:

  • I require session.rb which houses TwitterSession.
  • I pass in a yaml file to TwitterSession which represents a config store for my particular twitter account.