Twitter4R v0.2.4 Released

Written on 3:24:00 PM by S. Potter

Version 0.2.4 of Twitter4R was released last night with the main change that makes the library much easier to use in Ruby on Rails applications. To install/update Twitter4R v0.2.4 just use the following command: sudo gem install twitter4r For more information on how to integrate Twitter4R with Rails see the following links:

My Blackle Energy Consumption Experiment

Written on 11:14:00 AM by S. Potter

I received a forward from a contact earlier today touting the energy savings of using Blackle instead of Google to search. My immediate thought was surely there would be no difference for an LCD monitor, or would there? I can, of course, see for CRT monitors energy savings are highly likely. The following is the energy saving content from the email forward I received:

We probably all use google several times a day - here's something to consider: When your screen is white, be it an empty word page, or the Google page, your computer consumes 74 watts, and when its black it consumes only 59 watts. An article about the energy saving that would be achieved if Google had a black screen, taking into account the huge number of page views, worked out at a saving of 750 mega watts/hour per year. In a response to this article Google created a black version of its search engine, called Blackle, with the exact same functions as the white version, but with a lower energy consumption, check it out: http://www.blackle.com
Since I work for a very energy conscious firm and I have been cycling or walking EVERYWHERE around town this summer (except I allow myself use of my car one afternoon a week maximum), I was curious and had to get to the bottom of it.

Hypothesis

There will not be any significant change in power usage (aka wattage) when viewing Google.com in my web browser versus viewing Blackle.com in the same web browser window. (I must confess I already knew how LCD monitors worked before conducting this experiment, but there are a surprising number of people who do not believe the backlight of an LCD monitor is on for the entire LCD regardless of what is being shown - ignorance is bliss for some).

Conditions

  • Dimensions of browser on screen must remain constant for both page views
  • Power reader is monitored for 30 seconds while each page is being displayed and all reading changes will be noted

Equipment

  • P3 Kill-A-Watt reader (x1)
  • 17" Dell LCD monitor (x1)
  • [Indirectly used] Dell workstation (x1)
  • Cell phone (to time page view durations)

Preparation

  1. Power off LCD monitor and unplug from power supply
  2. Plug in power reader into power supply and plug LCD monitor plug into power reader socket
  3. Select power reading (can choose from voltage, power and current "real-time" readings)
  4. Record initial "power off" reading
  5. Power on LCD monitor
  6. Record initial "power on" reading

Method

  1. Open web browser and maximize window
  2. Surf to http://google.com
  3. Start cell phone timer as soon as page is loaded
  4. Monitor power reading for 30 seconds and note any changes to reading
  5. In same web browser window surf to http://blackle.com
  6. Start cell phone timers as soon as page is loaded
  7. Monitor power reading for 30 seconds and note any changes to reading

Results

Initial "power off" reading: 0W (zero Watts) Initial "power on" reading: 20W (twenty Watts) after 3-4 seconds Google reading: started at 20W, no change for 30 seconds, ended at 20W. Blackle reading: started at 20W, no change for 30 seconds, ended at 20W.

Analysis

As we can see there was no significant change in the reading (to stated equipment error of plus or minus 0.2%). Therefore our hypothesis is shown to hold in this experiment using the equipment identified above and I conclude that Blackle has next to zero impact on energy consumption of LCD monitors. The good news is that my LCD monitor consumes much less than Blackle's claim of 50-75 Watts regardless of the search engine presentation I use!:)

Twitter4R v0.2.3 Released

Written on 12:50:00 PM by S. Potter

As promised earlier this week Twitter4R v0.2.3 made it out this weekend. Twitter4R v0.2.3 has just been uploaded to Rubyforge in Ruby Gem, tgz and zip formats. It may take up to several hours for the Rubyforge download mirrors to sync, so you may not be able to download it for a while. To upgrade/install using Ruby Gems: $ sudo gem install twitter4r I am in the process of updating the project website. So bear with me while I take care of this. Release Notes:

  • Fixed defect #31 such that passing string screen name as for user argument is handled correctly.
  • Fixed #30 typo: respond_to -> respond_to?
  • Added relevant exception handling for #message(:post, ...) case #32
  • Add ability to pass in Twitter::User object to Twitter::Client#user(...) #33
  • Added stats Rake task
  • Updated RDoc for Twitter::Client#user to warn against using it to get followers of authenticated user and updated ArgumentError raising logic as per #29.
Enjoy!

Twitter4R v0.2.2 Released

Written on 5:21:00 PM by S. Potter

As solo already noted in the comments for the Twitter4R v0.2.1 Released blog entry from yesterday, version 0.2.2 is already here:) I usually wait 4 hours before I blog about it to let the Rubyforge gem servers sync. Changes in the version 0.2.2 release include:

  • Fixed URI paths for user, messaging and friendship APIs (#25)
  • Added action checks for Twitter::Client methods: #user, #my, #message, #messages, #status, #timeline, #friend (#26)
  • Added 'source' configuration documentation.
  • Added missing attributes for Twitter::User (#28)
The most notable thing is that I add the protected and profile_image_url attributes to the Twitter::User model class, which were missing previously.

Twitter4R v0.2.1 Released

Written on 10:30:00 AM by S. Potter

To install/update Ruby gem use: sudo gem install twitter4r OR sudo gem update twitter4r Note: It may take some hours before the Rubyforge mirrors are in sync. The ‘source’ feature was added to Twitter v0.2.1 such that in the web interface Twitter users can see which application the messages were sent from. The default is ‘twitter4r’ with link ‘http://twitter4r.rubyforge.org’. You can change this, but you will also need to contact Twitter developers (not Twitter4R) to set it up for you on the server side. To set relevant client configuration in Twitter4R you can do the following:


require('rubygems')
gem('twitter4r', '>=0.2.1')
require('twitter')

Twitter::Client.configure do |conf|
  conf.source = 'mysourceidfromtwitter'
end
Remember you will need to contact the Twitter developers to setup a source id and url with them before the above code snippet will start working. Enjoy!

Twitter4R v0.2.0: Friendship API

Written on 8:15:00 AM by S. Potter

After a week I realized I had not yet written about the Friendship API in Twitter4R and it is one of the simpler the parts of the core library. Below shows some code to add and remove a friend:


require('rubygems')
gem('twitter4r', '>=0.2.0')
require('twitter')

client = Twitter::Client.new(:login => 'mylogin', :password => 'mypasswd')
# we can add a friend by using their unique integer id or user object.
screen_name = 'myfriend'
user = Twitter::User.find(screen_name)
id = user.id

# Like any of the following....
client.friend(:add, user)
client.friend(:add, id)

# We can also use any of the following APIs to do exactly the same thing...
client.friend(:remove, user)
client.friend(:remove, id)
See related Twitter4R RDoc.

Twitter4R v0.2.0: Extras API

Written on 6:44:00 PM by S. Potter

There are a couple of miscellaneous features in Twitter4R that I wasn't comfortable officially supporting like the featured users method in the Twitter REST API that is documented to do one thing and implemented to do another by the actual Twitter developers. Obviously I don't control what the Twitter developers do, so while I wanted to support this REST API in the library to achieve 100% Twitter REST API coverage I also wanted to emphasize that if the Twitter REST API implementation changes to work as documented then Twitter4R may not work well or at all for this method. To get an Array of Twitter::User objects that represent Twitter's current featured users we would write code like:


require('rubygems')
gem('twitter4r', '0.2.0')
require('twitter')

# Now we must include this extra file import or we will get a NoMethodError
require('twitter/extras')

client = Twitter::Client.new
users = client.featured(:users)
Also in the Twitter4R Extras API is a class helper method added to Twitter::Client called from_config which takes up to two arguments. Here is an example:

require('rubygems')
gem('twitter4r', '0.2.0')
require('twitter')

# I have a 'twitter.yml' file in subdir 'config' that I will load credential from.
# This feature of Twitter::Client is in the Extras API and you need to require
# twitter/console to access it.
require 'twitter/console'
config_file = File.join(File.dirname(__FILE__), 'config', 'twitter.yml')
twitter = Twitter::Client.from_config(config_file)

# By default the from_config method selects the credentials defined
# for the 'test' environment.  The twitter.yml file will look something like this:
#  test:
#    login: mylogin
#    password: mypasswd
# If, however, we wanted another environment's credentials to be used we pass that 
# in as the second argument to from_config like so...
twitter = Twitter::Client.from_config(config_file, 'dev')
Again here is a link to the Twitter4R v0.2.0 RDoc.

Twitter4R v0.2.0: User API

Written on 6:33:00 PM by S. Potter

The User API provides an easy Ruby API that maps onto a less consistent Twitter REST API, which is good news for Twitter4R users:) The following example code snippet shows how you would use the Twitter4R User API:


require('rubygems')
gem('twitter4r', '0.2.0')
require('twitter')

# I have a 'twitter.yml' file in subdir 'config' that I will load credential from.
# This feature of Twitter::Client is in the Extras API and you need to require
# twitter/console to access it.
require 'twitter/console'
config_file = File.join('config', 'twitter.yml')
twitter = Twitter::Client.from_config(config_file)

# Gets the Twitter::User object for user with screen name otherlogin.
user = twitter.user('otherlogin')
# Gets an Array of Twitter::User objects that represent the friends of user 
# with screen name otherlogin.
friends = twitter.user('otherlogin', :friends)

# Gets the Twitter::User object for the user we used to authenticate above
# using the Twitter::Client.from_config method in the Extras API above.
me = twitter.my(:info)

# Gets the Array of Twitter::User objects that represent the followers of 
# the user whose credentials were used to authenticate with the Twitter service.
myfollowers = twitter.my(:followers)
# The same can be done for friends, i.e.
myfriends = twitter.my(:friends)

# Alternative we can use the equivalent Model APIs like so....
myfollowers = me.followers
myfriends = me.friends
Hope you find this helpful. If you want to learn more about the User API you should consult the Twitter4R v0.2.0 RDoc.

Twitter4R v0.2.0: Messaging API

Written on 9:18:00 AM by S. Potter

Probably one of the more recent additions to the Twitter REST API has been that of direct messaging. In the Twitter web interface we were introduced to the supported construct of direct messaging only a couple of months ago. The Messaging API in Twitter4R provides access to these new supported features via two instance methods on the Twitter::Client class: #message and #messages. Below we can see an example of a common non-trivial usage of this API:


gem('twitter4r', '>=0.2.0')
require('twitter')

client = Twitter::Client.new(:login => 'mylogin', :password => 'mypassword')
received_messages = client.messages(:received)
# Now do whatever it is you wish to do with the Array returned and assigned to received_messages.
# It is an Array of Twitter::Message.  Twitter::Message has the following attributes:
# * sender (Twitter::User)
# * recipient (Twitter::User)
# * text (String)
# * created_at (Time)

new_message = client.message(:post, 'I am addicted to Twitter', 'myfriendslogin')
# Note: if we had a handle to our friend's Twitter::User object we could substitute that object
# for the screen name screen given (i.e. 'myfriendslogin' in the above example).  This would 
# look like:
#  new_message = client.message(:post, 'I am addicted to Twitter', user)

# Now we realized sending that message was a big mistake, so we try to delete is quickly:
deleted_message = client.message(:delete, new_message)
To find out more about the Messaging API of Twitter4R visit the v0.2.0 RDoc and select the examples/messaging.rb link from the top left frame. Also note that you can create new direct messages via the Model API of Twitter4R as well. Happy direct messaging on Twitter using Twitter4R.

Twitter4R v0.2.0: Model API

Written on 8:53:00 AM by S. Potter

For those that are familiar with the ActiveRecord API, the Model API of Twitter4R should feel fairly natural for you. Currently the raw Twitter REST API inherently exposes three types of models:

  • Status
  • User
  • Message
These model classes are available via the Twitter4R classes Twitter::Status, Twitter::User and Twitter::Message respectively. What you will notice on these classes is that they have the familiar find and create class methods that behave almost identically to ActiveRecord::Base subclasses (or models as we call them in Rails applications). There is one difference, due in part to the slightly different way "connections" to the Twitter4R "datasource" is handled. So below is a non-trivial example of using these ActiveRecord style methods:

gem('twitter4r', '>=0.2.0')
require('twitter')

client = Twitter::Client.new(:login => 'mylogin', :password => 'mypassword')
# I want to post a new status to my timeline, which can also be do by:
#  client.status(:post, 'My new status message')
status = Twitter::Status.create(:client => client, :text => 'My new status message')

user = Twitter::User.find('dictionary', client)
message = Twitter::Message.create(:client => client, :recipient => user, :text => 'canadaphile')
What we see here is very similar to ActiveRecord style models, but not quite. In the find method invoked on Twitter::User above we supplied a second argument, which is the client (the client context or "connection") object. We also make sure to pass in a :client key-value pair in each create method called above. Without it an ArgumentError would be raised by each create call. A few notes:
  • Twitter::User does not define a meaningful create method since Twitter doesn't allow the creation of new user account via their REST API
  • Twitter::User model has some class and instance helper methods, which are described more in the RDoc (see link below)
See the v0.2.0 RDoc for Twitter4R and select the examples/model.rb link from the top left frame.

Twitter4R v0.2.0: Status API

Written on 9:35:00 PM by S. Potter

The Status API deals with access to single status objects on the Twitter server. Since "updating" (in the traditional sense) a status in Twitter is non-existent (you update your timeline with a brand new status, but you cannot edit individual status text after created), Twitter4R provide a CRD-style interface (rather than true CRUD-style). For example, to post a new status to your timeline you might write code like the following:


gem('twitter4r', '>=0.2.0')
require('twitter')
client = Twitter::Client.new(:login => 'mylogin', :password => 'mypassword')
status = client.status(:post, 'Learning the Twitter4R Status API in 60 seconds.')
To retrieve the full status object (Twitter::Status instance - see Twitter4R Model API documentation) given a unique status ID you can code:

# Assume code in previous code snippet
status = client.status(:get, 140684282) # => should be announcement status of Twitter4R v0.2.0 release
# Now you can query the attributes of the status like...
puts "#{status.user.screen_name}: #{status.text} @ #{status.created_at}"
To delete the status we initially posted above (or any other status of yours that you have a handle to (a handle can be just the unique integer status ID or the status object itself) we can code:

# Again assume code from first code snippet above...
client.status(:delete, status) # here we can pass in the full object or just the id, it doesn't matter.  Same as in #status(:get, ...) case.

Upgrading from 0.1.x

To upgrade from Twitter4R v0.1.x you probably need to replace code like:

client.update('My status message text')
With code like the following:

client.status(:post, 'My status message text')
There were no APIs in Twitter4R v0.1.x for the single status :get and :delete use cases.

Twitter4R v0.2.0: Timeline API

Written on 9:06:00 PM by S. Potter

The Timeline API segment of Twitter4R provides access to the REST API that deals with public, friend and [public] user status timelines. For example, to get an Array of statuses representing your own timeline on Twitter all you would need to type was:


gem('twitter', '>=0.2.0')
require('twitter')
client = Twitter::Client.new(:login => 'mylogin', :password => 'mypassword')
timeline = client.timeline_for(:me)
If your timeline is public and you do not need to invoke any authenticated methods, you do not need to pass in login/password credentials to the Twitter::Client constructor, just Twitter::Client.new is all that is needed. Other than the :me timeline, the following are available:
  • :public - returns the public timeline on Twitter (last 20 statuses)
  • :friends - returns a timeline of all your friends' statuses
  • :friend - returns a timeline of one particular friend
  • :user - returns timeline of one particular user
Different options are available with each of the timelines described above. To read more consult the Twitter v0.2.0 official RDoc documentation.

Upgrading from 0.1.x

If you are upgrading from Twitter4R v0.1.x you will probably have code that looks similar to the following:

client = Twitter::Client.new(:login => 'mylogin', :password => 'mypassword')
timeline = client.public_timeline
All you will need to do is change the last (second) line above to:

timeline = client.timeline_for(:public)

Twitter4R v0.2.0: Configuration API

Written on 2:48:00 PM by S. Potter

The Configuration API allows applications using Twitter4R configure static connection options such as:

  • connection protocol (e.g. HTTP and SSL)
  • host name and port (good for testing or if the Twitter REST API moved to a different subdomain)
  • proxy settings (i.e. proxy host, port, username and password)
  • user agent identifier
  • X-Twitter-Client* HTTP headers for Twitter's internal usage analysis
This API intentionally tries to look like the Rails initializer/configuration API:

require('rubygems')
gem('twitter4r', '0.2.0')
require('twitter')

Twitter::Client.configure do |conf|
  # We can set Twitter4R to use :ssl or :http to connect to the Twitter API.
  # Defaults to :ssl
  conf.protocol = :ssl

  # We can set Twitter4R to use another host name (perhaps for internal
  # testing purposes).
  # Defaults to 'twitter.com'
  conf.host = 'twitter.com'

  # We can set Twitter4R to use another port (also for internal
  # testing purposes).
  # Defaults to 443
  conf.port = 443

  # We can set proxy information for Twitter4R
  # By default all following values are set to nil.
  conf.proxy_host = 'myproxy.host'
  conf.proxy_port = 8080
  conf.proxy_user = 'myuser'
  conf.proxy_pass = 'mypass'

  # We can also change the User-Agent and X-Twitter-Client* HTTP headers
  conf.user_agent = 'MyAppAgentName'
  conf.application_name = 'MyAppName'
  conf.application_version = 'v1.5.6'
  conf.application_url = 'http://myapp.url'
end
If using Twitter4R (open source Ruby library for Twitter REST API) in a Rails application I would recommend adding the necessary Twitter4R configuration code in config/twitter.rb then including it in the config/environment.rb file to keep configurations separate.

Upgrading from 0.1.1

In Twitter4R v0.1.1 you could configure protocol, server host, server port and proxy settings using the following code:
Twitter::Client.conf(
  :ssl => true, 
  :port => 443, 
  :proxy_host => 'myproxy.host', 
  :proxy_port => 8080)
This API no longer exists, so if upgrading to Twitter4R 0.1.1 you will need to change the above to something like the following:
Twitter::Client.configure do |conf|
  conf.protocol = :ssl
  conf.port = 443
  conf.proxy_host = 'myproxy.host'
  conf.proxy_port = 8080
end
This change shouldn't be too painful for people I hope. Please refer to the Twitter4R v0.2.0 RDoc for more information.

Twitter4R 0.2.0 Release

Written on 12:01:00 PM by S. Potter

It's been almost two weeks, but Twitter4R had some major refactoring and updating between 0.1.1 and 0.2.0. So I hope you will think the wait was worth it. As usual you can grab the latest Ruby Gem (once all Rubyforge mirrors have synced) with: sudo gem install twitter4r. I am still in the process of pushing out the new Ruby Gem to Rubyforge and updating the website for the project, but the release is code complete. Some changes are quite significant in some areas. The basic idea of this release (0.2.0) was to cover the official published Twitter REST API 100% and remove some of the clutter the first two releases of Twitter4R introduced partly due to mirroring Twitter's inconsistent API. In the same fashion as both previous releases of Twitter4R, version 0.2.0 has 100% C0 code coverage via RSpec specifications. I separated out Twitter4R library into various API segments:

I will write a blog entry as an introduction to each segment over the next couple of days. In the meantime I will finish publishing the new website documentation and leave you with the RDoc for version 0.2.0. Updates:

Twitter API's unRESTfulness causing trouble

Written on 11:07:00 AM by S. Potter

I am now designing my sixth (6th) set of web services on my new project. The previous five (5) and this current venture were/are for the purpose of allowing partners and internal developers to integrate with a "platform" of some kind. These clients varied from Fortune 500 companies, prestigious privately held firms and most recently a VC-funded startup. The previous two web service APIs were based on the REST "architecture" (yes, I know that word gets used too much, but I use it now for lack of a better word at this moment). The previous REST API was in fact fully RESTful and my new software product design also has the aim of RESTfulness at all points. During my travels meandering through the windy trails of designing REST APIs, I have realized just how important being truly RESTful is to REST APIs. Yes, reread that sentence again. if it doesn't make sense I recommend reading RESTful Web Services, which should clarify this distinction for you. For me, the biggest plus of RESTful web services is that it promotes consistency at all levels. Moving on...where was I going? Ah, yes, Twitter... The routes.rb for twitter.com seem to be a little misleading. It seems a mix and match approach was taken to developing a "RESTful" API for twitter.com using Rails. The current RESTful Rails convention is the following (in a nutshell):

  • GET /resources/:id.:format => get information about resource with specified :id
  • POST /resources => create new resource using data given in request body
  • PUT /resources/:id.:format => update resource with specified :id using data given in request body
  • DELETE /resources/:id.:format => delete resource with specified :id and return resource in :format given
This is all great, however, in twitter.com it seems there was a previous ugly step-child of a "RESTful" design implemented before Rails 1.2 standardized on the above approach (just speculating here). The evidence of this exists in multiple places in the Twitter API's inconsistency, however, one case in particular creates a defect in my view (and I am being kind by not considering all RESTful violations as defects, since the Twitter developers need to cater to the weekend PHP and VB.NET hackers that might not care about the beauty of REST's simple yet consistent design and purpose - I will give the Twitter developers the benefit of the doubt for this article): In the users#show API there seems to be a conflict. For example, GET /users/show.json?id=mylogin will, in fact, return the user information for user with screen_name ‘show’ instead of ‘mylogin’, which is obviously not the intention of the above HTTP call, especially when /users/show is an advertised way to access user information in the official Twitter API docs. Yes, I know I can call GET /users/mylogin.json and be done with it, but the misleading defect still remains. Interestingly, GET /users/show?id=mylogin does yield the expected result assuming you have the Accept HTTP header set to the appropriate mime-type (e.g. 'text/x-json'). Screen names that cause potential conflicts (if using Rails defaults) are: create, destroy, update, new, show. All of which exist at twitter.com. My point here isn't to blast Twitter developers necessarily, simply to ask all developers (or if you must title yourself this way "architects") to consider the importance of creating consistent APIs, preferably based on open standards/recommendations like REST. And most importantly, if a REST API isn't truly "RESTful", don't say it is!:) You will get more respect for being honest from people that know what it really means. Related post:
  • Twitter4R v0.2.0 Released - the ultimate open source library that provides Ruby bindings for 100% of the officially documented Twitter REST API.

Marrying Autotest with RSpec on Gnome

Written on 5:54:00 PM by S. Potter

For those that don't know marrying Autotest with RSpec is a God send and I highly recommend it for pure Ruby as well as Rails application development. On the Mac OS X and KDE desktop environments there are builtin Autotest extensions that visually flag erroneous or successful RSpec runs with a standard desktop notification message. For Gnome users on Linux running Autotest with RSpec you may have seen a couple of ${HOME}/.autotest customizations out there that don't work with RSpec v1.x+. Below is my ${HOME}/.autotest that works with RSpec 1.0.5:


require('autotest/redgreen')
require('autotest/timestamp')

module Autotest::GnomeNotify

  # Time notification will be displayed before disappearing automatically
  EXPIRATION_IN_SECONDS = 3
  ERROR_STOCK_ICON = "gtk-dialog-error"
  SUCCESS_STOCK_ICON = "gtk-dialog-info"

  RE_RSPEC_SUMMARY = Regexp.new(/(\d+) examples, (\d+) failure/)

  class << self
    # Convenience method to send an error notification message
    #
    # [stock_icon]   Stock icon name of icon to display
    # [title]        Notification message title
    # [message]      Core message for the notification
    def notify(stock_icon, title, message)
      options = "-t #{EXPIRATION_IN_SECONDS * 2500} -i #{stock_icon}"
      system "notify-send #{options} '#{title}' '#{message}'"
    end

    def compose_message(at)
      specs, failures = 0, 0
      at.results.scan(RE_RSPEC_SUMMARY) do |s, f|
        specs = s.to_i
        failures = f.to_i
      end
      "#{specs} specs, #{failures} failures"
    end
  end

  Autotest.add_hook :red do |at|
    notify ERROR_STOCK_ICON, "Some specs failed.", compose_message(at)
  end

  Autotest.add_hook :green do |at|
    notify SUCCESS_STOCK_ICON, "All specs passed.  Have a beer!", compose_message(at)
  end
end
I assume there this will work with RSpec 1.x, but I have only tested with RSpec 1.0.5. Enjoy!

RSpec "should_yield"?

Written on 4:51:00 PM by S. Potter

While writing some specifications for the 0.2.0 version of Twitter4R (soon to be released - honestly), I realized there was no clean way to do #should_yield, so I thought I would offer my suggestion for the developers of RSpec to critique as appropriate. Assume we have a method signature that takes a block and should yield an object to the block given (if one is given). Something like the following:

class Client
  class << self
    def configure(&block)
      # implementation of Client.configure
    end
    # rest of class methods for Client class go here...
  end
  # rest of instance methods for Client class go here....
end
Where usage for the Client.configure method should look something like the following:
Client.configure do |conf|
  conf.proxy_host = 'myproxyhost'
  conf.proxy_port = 8080
  conf.proxy_user = 'me'
  conf.proxy_pass = 'mypass'
end
The object yielded to the block given is an instance of Config which has settable attributes: proxy_host, proxy_port, proxy_user, proxy_pass. The question is how do we write RSpec specifications for this? First we need to define what we want to specify for the expected behavior. My first attempt is to describe (in English) that:
  • Client.configure should
    • accept a block as argument
    • yield a Config instance to the given block
The way I did this in the Twitter4R (open source Ruby library for the Twitter REST API) project specifications was something like the following:
describe Client, ".configure" do
  before(:each) do
    @has_yielded = false
    @block = Proc.new do |conf|
      conf.is_a?(Config).should be_true
      @has_yielded = true
    end
  end

  it "should accept a block as argument" do
    lambda {
      Client.configure {}
    }.should_not raise_error
  end

  it "should yield a Config object to given block" do
    Client.configure(&block)
    @has_yielded.should be_true
  end

  after(:each) do
    @has_yielded, @block = nil
  end
end
What I would really like to do something like the following instead:
describe Client, ".configure" do
  it "should accept a block as argument" do
    lambda {
      Client.configure {}
    }.should_not raise_error
  end

  it "should yield a Config object to given block" do
    lambda {
      Client.configure(&spec_block(1))
    }.should yield_with(Config)
  end
end
I haven't extended the Proc class to this point, but if/when I do I will post it to this blog and perhaps also to the RSpec mailing list. If you have suggestions on how to improve the more manual way (the first RSpec code listing above), please do let me know. I have not yet played around with inheritable describe blocks yet, but I have hunch that would DRY up some of the code for if needed for many contexts. Alternatively if you have suggestions on how to improve the second RSpec code listing (directly above) to be more clear, please do let me know that too. I realize the developers of RSpec don't want to have too many #should_XXX methods, so I am open to suggestions (since I also agree with that notion). UPDATE: I created a new feature request and dialog with RSpec developers at: [#11949]