Daily Code Reading #21 – RestClient binary

This week I’m going to read through the code for RestClient. RestClient is a simple HTTP and REST client for Ruby that can be used to consume RESTful resources. I’ve used it in a Single Sign On system I built for Redmine, redmine_sso_client and redmine_sso_server.

Today I started with the command line binary for RestClient. There are two things the binary does, creates a request from the command line augments or opens an IRB shell to run commands interactively.

The Code for creating a request

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def r
    @r ||= RestClient::Resource.new(@url, @username, @password)
end
 
if @verb
    begin
        if %w(put post).include? @verb
            puts r.send(@verb, STDIN.read)
        else
            puts r.send(@verb)
        end
        exit 0
    rescue RestClient::Exception => e
        puts e.response.body if e.respond_to? :response
        raise
    end
end

This code is run after the initialization blocks in restclient and it handles sending the request to the server. @verb should be ‘get’, ‘post’, ‘put’, or ‘delete’ so they are sent directly into the RestClient::Resource as method calls (e.g. r.send('get')). put and post methods will also pass in the standard input as additional headers for the request.

On a successful request, the response will be printed by the puts. Invalid or unsuccessful requests will raise a RestClient::Exception which will print out the response body and then exit the program.

The Code for the interactive IRB shell

The following three code snippets create RestClient‘s interactive IRB shell.

1
2
3
4
5
6
7
%w(get post put delete).each do |m|
 eval <<-end_eval
def #{m}(path, *args, &b)
    r[path].#{m}(*args, &b)
end
 end_eval
end

This bit of metaprogramming creates four methods that are proxied to the RestClient::Resource object: #get, #post, #put, and #delete. This will let the shell have more of a DSL feel to it.

1
2
3
4
5
6
7
8
9
def method_missing(s, *args, &b)
    super unless r.respond_to?(s)
    begin
        r.send(s, *args, &b)
    rescue RestClient::RequestFailed => e
        print STDERR, e.response.body
        raise e
    end
end

Using method_missing RestClient is able to proxy all of the other methods to the RestClient::Resource object, as long as that object responds to that method. So calling user in the shell will really call RestClient::Resource#user.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
require 'irb'
require 'irb/completion'
 
if File.exists? ".irbrc"
    ENV['IRBRC'] = ".irbrc"
end
 
if File.exists?(rcfile = "~/.restclientrc")
    load(rcfile)
end
 
ARGV.clear
 
IRB.start
exit!

Finally, the binary loads several optional configuration files and starts IRB.

What I found interesting was how simple it was to add a custom shell using IRB. Using this code, I might be able to do something similar with redmine_client to create a “shell” for Redmine.