May 18, 2009
Older: Include vs Extend in Ruby
Newer: What If A Key Value Store Mated With A Relational Database System?
Swine Flu and the Twitter Gem
I had some extra time today and I’ve been spotty on open source work over the past few weeks, so I decided to add support for the Twitter trends API to my Twitter gem.
Using HTTParty, the code for this turned out to be insanely simple, so short, in fact, that I’ll just put it inline here so you don’t even have to go over to Github. Aww, I’m so nice.
module Twitter
class Trends
include HTTParty
base_uri 'search.twitter.com/trends'
format :json
# :exclude => 'hashtags' to exclude hashtags
def self.current(options={})
mashup(get('/current.json', :query => options))
end
# :exclude => 'hashtags' to exclude hashtags
# :date => yyyy-mm-dd for specific date
def self.daily(options={})
mashup(get('/daily.json', :query => options))
end
# :exclude => 'hashtags' to exclude hashtags
# :date => yyyy-mm-dd for specific date
def self.weekly(options={})
mashup(get('/weekly.json', :query => options))
end
private
def self.mashup(response)
response['trends'].values.flatten.map { |t| Mash.new(t) }
end
end
end
Pure TDD
I am most definitely a tester, but I’ll admit I usually write code and then write the test. Of late, I’ve been reversing this trend and actually practicing TDD in full force by writing a small test, then only enough code to make it pass, followed by another test or more code for the existing test, finished with just enough code to make the new addition pass.
It is a different mindset to code in this way, compared to my code first and then make sure my butt is covered method and I’ve loving it. I thought I would find pure TDD tedious, but on the contrary, I think I’m coding faster and cleaner.
The Tests
So how did I test the code above? Again, inline for your viewing pleasure, are the tests I added to make sure I don’t break something in the future and get yelled at. Feel free to take a gander and I’ll meet back up with you at the bottom of it.
require File.dirname(__FILE__) + '/../test_helper'
class TrendsTest < Test::Unit::TestCase
include Twitter
context "Getting current trends" do
should "work" do
stub_get('http://search.twitter.com:80/trends/current.json', 'trends_current.json')
trends = Trends.current
trends.size.should == 10
trends[0].name.should == '#musicmonday'
trends[0].query.should == '#musicmonday'
trends[1].name.should == '#newdivide'
trends[1].query.should == '#newdivide'
end
should "be able to exclude hashtags" do
stub_get('http://search.twitter.com:80/trends/current.json?exclude=hashtags', 'trends_current_exclude.json')
trends = Trends.current(:exclude => 'hashtags')
trends.size.should == 10
trends[0].name.should == 'New Divide'
trends[0].query.should == %Q(\"New Divide\")
trends[1].name.should == 'Star Trek'
trends[1].query.should == %Q(\"Star Trek\")
end
end
context "Getting daily trends" do
should "work" do
stub_get('http://search.twitter.com:80/trends/daily.json?', 'trends_daily.json')
trends = Trends.daily
trends.size.should == 480
trends[0].name.should == '#3turnoffwords'
trends[0].query.should == '#3turnoffwords'
end
should "be able to exclude hastags" do
stub_get('http://search.twitter.com:80/trends/daily.json?exclude=hashtags', 'trends_daily_exclude.json')
trends = Trends.daily(:exclude => 'hashtags')
trends.size.should == 480
trends[0].name.should == 'Star Trek'
trends[0].query.should == %Q(\"Star Trek\")
end
should "be able to get for specific date (with date string)" do
stub_get 'http://search.twitter.com:80/trends/daily.json?date=2009-05-01', 'trends_daily_date.json'
trends = Trends.daily(:date => '2009-05-01')
trends.size.should == 440
trends[0].name.should == 'Swine Flu'
trends[0].query.should == %Q(\"Swine Flu\")
end
should "be able to get for specific date (with date object)" do
stub_get 'http://search.twitter.com:80/trends/daily.json?date=2009-05-01', 'trends_daily_date.json'
trends = Trends.daily(:date => Date.new(2009, 5, 1))
trends.size.should == 440
trends[0].name.should == 'Swine Flu'
trends[0].query.should == %Q(\"Swine Flu\")
end
end
context "Getting weekly trends" do
should "work" do
stub_get('http://search.twitter.com:80/trends/weekly.json?', 'trends_weekly.json')
trends = Trends.weekly
trends.size.should == 210
trends[0].name.should == 'Happy Mothers Day'
trends[0].query.should == %Q(\"Happy Mothers Day\" OR \"Mothers Day\")
end
should "be able to exclude hastags" do
stub_get('http://search.twitter.com:80/trends/weekly.json?exclude=hashtags', 'trends_weekly_exclude.json')
trends = Trends.weekly(:exclude => 'hashtags')
trends.size.should == 210
trends[0].name.should == 'Happy Mothers Day'
trends[0].query.should == %Q(\"Happy Mothers Day\" OR \"Mothers Day\")
end
should "be able to get for specific date (with date string)" do
stub_get 'http://search.twitter.com:80/trends/weekly.json?date=2009-05-01', 'trends_weekly_date.json'
trends = Trends.weekly(:date => '2009-05-01')
trends.size.should == 210
trends[0].name.should == 'TGIF'
trends[0].query.should == 'TGIF'
end
should "be able to get for specific date (with date object)" do
stub_get 'http://search.twitter.com:80/trends/weekly.json?date=2009-05-01', 'trends_weekly_date.json'
trends = Trends.weekly(:date => Date.new(2009, 5, 1))
trends.size.should == 210
trends[0].name.should == 'TGIF'
trends[0].query.should == 'TGIF'
end
end
end
So, yeah, nothing earth shattering. It feels a bit repetitive, but I don’t mind some amount of repetition in my tests. The fixture files were created quite simply using curl.
cd test/fixtures
curl http://search.twitter.com:80/trends/weekly.json?date=2009-05-01 > trends_weekly_date.json
# rinse and repeat for each file
The stub_get method is a simple wrapper around FakeWeb and looks something like this:
def stub_get(url, filename, status=nil)
options = {:string => fixture_file(filename)}
options.merge!({:status => status}) unless status.nil?
FakeWeb.register_uri(:get, url, options)
end
def fixture_file(filename)
file_path = File.expand_path(File.dirname(__FILE__) + '/fixtures/' + filename)
File.read(file_path)
end
I’m lazy and find that stub_get is much shorter than FakeWeb.register_uri blah, blah, blah. The tests use FakeWeb, shoulda and my fork of matchy, in case you are curious.
Example Uses
So what can you do with the new trends addition? Below are some examples of how you can obtain trend information.
Twitter::Trends.current
Twitter::Trends.current(:exclude => 'hashtags')
Twitter::Trends.daily # current day
Twitter::Trends.daily(:exclude => 'hashtags')
Twitter::Trends.daily(:date => Date.new(2009, 5, 1))
Twitter::Trends.weekly # current day
Twitter::Trends.weekly(:exclude => 'hashtags')
Twitter::Trends.weekly(:date => Date.new(2009, 5, 1))
That’s all for now. Enjoy the new trends and build something cool. Oh, and if you want to play with trends, but don’t have an idea, I have one and most likely won’t have time to build it. I’d be happy to collaborate.
2 Comments
May 19, 2009
Sorry for this possibly stupid question, but I don’t understand how the get_stub is used with the matchers.
How are the results of the stubs are passed?
May 19, 2009
@Fadhli – The stub_get method makes it so that Twitter::Trends.current and such don’t actually hit the internet, but instead just return a fixture file locally so that the tests are predictable and run faster. FakeWeb does all the work behind the scenes. Check it out for more information.
Sorry, comments are closed for this article to ease the burden of pruning spam.