nuclearsandwich's

Mad scribblings and inexpert musing

How I Used a "Debugging Feature" of Pry to Build Cage

published: 17 May 2012 12:05:00AM

Pry is an alternative interactive Ruby environment similar to IRB. In addition to looking a little nicer through nearly live colorization, Pry offers some pretty powerful features for exploring, modifying, and debugging Ruby applications in a live system.

Pry's namesake ability to "pry" open an object is not limited to debugging use. It's actually a really straightforward way to build any kind of domain specific REPL. I think I just coined that term but it fits well and you'll soon see why.

The Backstory

When I working at NOWBOX I spent a lot of time hitting our API during ad-hoc testing. I was doing enough of this that just using curl wasn't cutting it.

Don't get me wrong, curl is awesome and super useful. But it lacks a few niceties. Chief among them curl, like HTTP, is stateless without the use of cookie files and given that we were using a token-based authentication system, simulating device-API interaction using exclusively curl was neither fun nor easy.

During this time, we were also using Rack::Test, which I really like. It's a solid and purposeful DSL and great at what it does. What it isn't good at is being interactive. I'd get colourful explosions(stacktraces) whenever I tried to pry into and look at a test using Rack::Test.

We already had the awesome Faraday included in our project, so I hacked together a quick rake task that would set up a Faraday::Connection and pry into it. What did this get me? I now had my interactive Rack::Test.

Cage Version -1

# Rakefile
task :client_console do
  require "faraday"
  require "pry"
  uri = case ENV["target"]
        when "production"
          "http://api.superawesomepage.com/1/"
        when "staging"
          "http://api.staging.superawesomepage.com/1/"
        else
          "http://localhost:3000/1/"
        end
  Faraday::Connection.new(uri).pry
end

Building the Gem

Pry made this really easy. So easy that later on, when I wanted to rewrite the above code and turn it into a reusable gem I kept the same strategy.

I build my DSL class, Cage::Console, which also handles all the initialization such as config file loading and default values for variables. It also overloads some of Pry's settings to get a more interesting prompt string and suppressing they default ~/.pryrc config file loading.

# lib/cage/console.rb
class Cage::Console
  def initialize config_file = nil
    configure_pry
    read_config_file File.expand_path config_file if config_file
    default_to_rubygems
    reinitialize_connection
  end

  ## The rest of the show
  ## ...
  ## The party starter
  def self.start! *args
    new(*args).pry
  end
end

The rest of the class is the domain specific stuff that deals with delegating to the Faraday connection object and building the response object. I built my own wrapper around Faraday's response so I could improve how they are displayed as return values.

# lib/cage/response.rb
class Cage::Response
  ## Delegating code
  ## ...
  ## Prettification code
  def inspect
    <<-PRETTY
Status: #{status}

Headers:
#{headers.map { |k, v| "  #{k}: #{v}" }.join "\n"}

Body:
  #{body}

#<Cage::Response:(#{url})>
      PRETTY
    end
  end
end

It's certainly verbose but the primary function is to show you the complete result of an HTTP request, which it does.

Concluding

That's basically all there is to it. This feature of Pry that's mainly advertised as a way to get hands-on with your objects for debugging is also a great way to build a domain specific or domain focused interactive environment. Depending on how much you want to customize Pry's interface you don't even need a thorough understanding of Pry's internals. If you're more interested in the features of Cage than the mechanics of its construction you can install it from RubyGems using gem install cage or check out its project page. The complete source for Cage is on GitHub. I hope you use Pry and Cage to build awesome stuff!