Archive for the ‘Ruby’ Category

Pivotal Tracker API Ruby Wrapper

November 11th, 2008

I have been playing around with the recently released public API for Pivotal Tracker. In the process of converting this into a Datamapper Adapter, I tested it with some simple Net::HTTP Ruby code. It provides a nice simple, and limited, illustration of what is possible with this public API. One thing I would like to see added to the API however is the ability to limit the number of results when querying with filters. Attached is the slightly modified source code (removed my project id and token key). Tests are included as always, but fair warning, some are brittle! I will post another revision once the Datamapper Adapter is created.


require 'rubygems'
require 'hpricot'
require 'net/http'
require 'uri'
require 'cgi' 

##
# Pivotal Tracker API Ruby Wrapper
# November 11, 2008
# Justin Smestad
# http://www.evalcode.com
##

class Tracker
  def initialize(project_id = changeme, token = changeme)
    @project_id, @token = project_id, token
  end

  def project
    resource_uri = URI.parse("http://www.pivotaltracker.com/services/v1/projects/#{@project_id}")
    response = Net::HTTP.start(resource_uri.host, resource_uri.port) do |http|
      http.get(resource_uri.path, {'Token' => @token})
    end

    doc = Hpricot(response.body).at('project')

    @project = {
      :name             => doc.at('name').innerHTML,
      :iteration_length => doc.at('iteration_length').innerHTML,
      :week_start_day   => doc.at('week_start_day').innerHTML,
      :point_scale      => doc.at('point_scale').innerHTML
    }
  end

  def stories
    resource_uri = URI.parse("http://www.pivotaltracker.com/services/v1/projects/#{@project_id}/stories")
    response = Net::HTTP.start(resource_uri.host, resource_uri.port) do |http|
      http.get(resource_uri.path, {'Token' => @token})
    end

    doc = Hpricot(response.body)

    @stories = []

    doc.search('stories > story').each do |story|
      @stories << {
        :id => story.at('id').innerHTML.to_i,
        :type => story.at('story_type').innerHTML,
        :name => story.at('name').innerHTML
       }
    end
    return @stories
  end

  # would ideally like to pass a size, aka :all to limit search
  def find(filters = {})
    uri = "http://www.pivotaltracker.com/services/v1/projects/#{@project_id}/stories"
    unless filters.empty?
      uri << "?filter="
      filters.each do |key, value|
        uri << CGI::escape("#{key}\"#{value}\""
      end
    end

    resource_uri = URI.parse(uri)
    response = Net::HTTP.start(resource_uri.host, resource_uri.port) do |http|
      http.get(resource_uri.path, {'Token' => @token})
    end

    doc = Hpricot(response.body)

    @stories = []

    doc.search('stories > story').each do |story|
      @stories << {
        :id => story.at('id').innerHTML.to_i,
        :type => story.at('story_type').innerHTML,
        :name => story.at('name').innerHTML
      }
    end
    return @stories
  end

  def find_story(id)
    resource_uri = URI.parse("http://www.pivotaltracker.com/services/v1/projects/#{@project_id}/stories/#{id}")
    response = Net::HTTP.start(resource_uri.host, resource_uri.port) do |http|
      http.get(resource_uri.path, {'Token' => @token, 'Content-Type' => 'application/xml'})
    end

    doc = Hpricot(response.body).at('story')

    @story = {
      :id => doc.at('id').innerHTML.to_i,
      :type => doc.at('story_type').innerHTML,
      :name => doc.at('name').innerHTML
    }
  end

  def create_story(story)
    story_xml = build_story_xml(story)
    resource_uri = URI.parse("http://www.pivotaltracker.com/services/v1/projects/#{@project_id}/stories")
    response = Net::HTTP.start(resource_uri.host, resource_uri.port) do |http|
      http.post(resource_uri.path, story_xml, {'Token' => @token, 'Content-Type' => 'application/xml'})
    end
  end

  def update_story(story)
    story_xml = build_story_xml(story)
    resource_uri = URI.parse("http://www.pivotaltracker.com/services/v1/projects/#{@project_id}/stories/#{story[:id]}")
    response = Net::HTTP.start(resource_uri.host, resource_uri.port) do |http|
      http.put(resource_uri.path, story_xml, {'Token' => @token, 'Content-Type' => 'application/xml'})
    end
  end

  def delete_story(story_id)
    resource_uri = URI.parse("http://www.pivotaltracker.com/services/v1/projects/#{@project_id}/stories/#{story_id}")
    response = Net::HTTP.start(resource_uri.host, resource_uri.port) do |http|
      http.delete(resource_uri.path, {'Token' => @token})
    end
  end

  private

    def build_story_xml(story)
      story_xml = "<story>"
      story.each do |key, value|
        story_xml << "<#{key}>#{value.to_s}</#{key}>"
      end
      story_xml << "</story>"
    end
end

Read the rest of this entry »

Posted in Datamapper, Development, Ruby | Comments (0)

Ruby’s and Rails’ case gotcha when comparing classes

October 15th, 2008

While pairing yesterday I ran into quite an interesting problem when using the Ruby case statement. Now this should not come to a surprise because the case statement uses the === operator rather than the == operator (that is common in other languages). We were refactoring some code like this.


variable = group_class == Animal ? 8 : 2

initially this turned into


if group_class == Animal
  variable = 8
elsif group_class == Water
  variable = 2
else
  variable = nil
end

This code was simply meant to check against class type since group_class is holding the type of this object. We decided to change this into a case statement as we could see more group_class types coming down the road in the next iteration.


variable = case group_class
               when Animal: 3
               when Water: 8
               ....

So this code was assumingly supposed to work, however does not. Apparently the valid way of writing this class is to do:


variable = case group_class.name
               when "Animal": 3
               when "Water": 8
               ....

Then this code works as designed. Tracking down the reason for this is ActiveRecord’s overloaded == and === operators.


# File activerecord/lib/active_record/base.rb, line 1269
1269:       def ===(object)
1270:         object.is_a?(self)
1271:       end
-----
# File activerecord/lib/active_record/base.rb, line 2421
2421:       def ==(comparison_object)
2422:         comparison_object.equal?(self) ||
2423:           (comparison_object.instance_of?(self.class) &&
2424:             comparison_object.id == id &&
2425:             !comparison_object.new_record?)
2426:       end

Just a quirk I noticed. Enjoy :)

Tags: , , ,
Posted in Rails, Ruby | Comments (1)

TextMate reigns supreme with ‘Ack in Project’

September 29th, 2008

The one thing holding TextMate back from Ruby / Rails domination is its horribly slow search functionality. Using ‘Search in Project’ will take what seems like an eternity to pop up any results. The only real attempt at solving this headache was to use ‘Grep in Project’, but it left a lot to be desired including search speed. Now comes the solution, ‘Ack in Project’ was a project created on GitHub back in August to use the much more efficient Ack library for searching. With enhanced output, it has given other IDEs like NetBeans and IntelliJ a swift quick to the balls as to what is the best Ruby / Rails editor.


$ cd /Applications/TextMate.app/Contents/SharedSupport/Bundles/
$ sudo git clone git://github.com/protocool/ack-tmbundle.git Ack.tmbundle

Use Ack in Project with Cmd+Shift+A

Tags: ,
Posted in Development, Rails, Ruby | Comments (18)

Rubyme opens its doors.

August 20th, 2008

I have been working long hours getting this site up and running. The concept was initially drafted after some tutoring work I had done and the difficulty I had in coordinating everything. If you have not yet figured out, the site is called Rubyme (www.rubyme.net). The goal of the site is to provide an easy way for users to find help on anything from specific project difficulties to a more typical tutor style. Currently many of the features are still under heavy development, but the basic functionality is there. I encourage you to sign up and become a tutor. It is a great way to make some money, get exposure, and learn. There will be ongoing patches, so please report any problems to my email address: justin.smestad@gmail.com

Tags: , , , , ,
Posted in Development, JRuby, Merb, Rails, Ruby | Comments (0)

mod_rails setup issues and solution

August 20th, 2008

So I was migrating my server over to Debian Lenny from Etch and ran into some interesting problems along the way. The first of which is related to an open ticket on the mod_rails issue tracker. The conflict appears to be with Wordpress permalinks, .htaccess and mod_rails. However, without my knowing when I installed mod_rails (a.k.a Passenger) via the script it disabled by rewrite module. So when I went back to check this blog, running wordpress, all the permalinks were broken. I had tried everything from setting ‘RailsAutoDetect off’ and ‘RailsAllowModRewrite on’, however neither of these are even needed. I just had to issue the simple commands:

sudo a2enmod rewrite
sudo /etc/init.d/apache2 restart

After these two commands, you can stop pulling out your hair and begin shaking your fist at the sneaky passenger/mod_rails installer. Just goes to show that automated installers are not always the holy grail.

Tags: , , , , , ,
Posted in Rails, Ruby | Comments (1)

Ruby 1.8.6 / 1.8.7 living side-by-side

July 17th, 2008

With the most recent update of OSX, Apple did not update Ruby from 1.8.6 to 1.8.7 even though there are significant improvements incorporated into the new version. I decided that instead of waiting for Apple to update my default Ruby install, I would do it myself. My goal was to keep the default Apple Ruby install of 1.8.6 alone and install 1.8.7 through MacPorts. That was the easy part.

$ sudo port install ruby
$ sudo port install rb-rubygems

This will give you both ruby and a rubygems install. The way to make this installation the default is to adjust your PATH to have /opt/local/bin/ as the first item in your PATH definition (which is set in ~/.bash_profile).

PATH=/opt/local/bin:$PATH

Then reload your terminal or execute:

$ source ~/.bash_profile

Now here comes a nicety that many people will really appreciate. We want both version of Ruby to share the same gems! This is simply set (adjust paths accordingly if your not on OSX 10.5) the following inside your .bash_profile.

export GEM_HOME=`/usr/bin/gem env home`
export GEM_PATH=`/usr/bin/gem env path`

Make sure you use ` marks and not ‘ or “. This will use your 1.8.6 ruby gems as the source for your ruby gems. There you go, now you have two versions of Ruby living side by side in harmony, its so beautiful it brings a tear to my eye.

The goal of this is that you can leave the Apple install alone and if you ever want new (more current) ruby versions, you can just issue an update command via MacPorts. Thanks to drbrain for the help on troubleshooting this setup.

Tags: , , ,
Posted in Ruby | Comments (1)