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.