Permalink
Browse files

Simplifying Rest API code to pass on RestClient and MultiJSON options

  • Loading branch information...
samlown committed Jun 25, 2011
1 parent dba06eb commit 974d2830392d61fe7f71bb178f131c49ef81e9cf
View
@@ -1,6 +1,7 @@
-== 1.1.0.pre4 - 2011-06-13
+== 1.1.0 - 2011-06-25
* Minor changes
+ * Refactored basic CouchRest API (get, post, etc.) to pass-through RestClient and MultiJSON options, including headers.
* CouchRest::Attributes module created to make attribute related methods independent.
== 1.1.0.pre3 - 2011-06-06
@@ -245,7 +245,7 @@ def update_doc(doc_id, params = {}, update_limit = 10)
def view(name, params = {}, payload = {}, &block)
payload['keys'] = params.delete(:keys) if params[:keys]
# Try recognising the name, otherwise assume already prepared
- view_path = name =~ /^([^_].+?)\/(.*)$/ ? "_design/#{$1}/_view/#{$2}" : name
+ view_path = name_to_view_path(name)
url = CouchRest.paramify_url "#{@root}/#{view_path}", params
if block_given?
if !payload.empty?
@@ -386,5 +386,11 @@ def encode_attachments(attachments)
def base64(data)
Base64.encode64(data).gsub(/\s/,'')
end
+
+ # Convert a simplified view name into a complete view path. If
+ # the name already starts with a "_" no alterations will be made.
+ def name_to_view_path(name)
+ name =~ /^([^_].+?)\/(.*)$/ ? "_design/#{$1}/_view/#{$2}" : name
+ end
end
end
View
@@ -1,69 +1,136 @@
module CouchRest
+ # CouchRest RestAPI
+ #
+ # The basic low-level interface for all REST requests to the database. Everything must pass
+ # through here before it is sent to the server.
+ #
+ # Five types of REST requests are supported: get, put, post, delete, and copy.
+ #
+ # Requests that do not have a payload, get, delete and copy, accept the URI and options parameters,
+ # where as put and post both expect a document as the second parameter.
+ #
+ # The API will try to intelegently split the options between the JSON parser and RestClient API.
+ #
+ # The following options will be recognised as header options and automatically added
+ # to the header hash:
+ #
+ # * :content_type, type of content to be sent, especially useful when sending files as this will set the file type. The default is :json.
+ # * :accept, the content type to accept in the response. This should pretty much always be :json.
+ #
+ # The following request options are supported:
+ #
+ # * :method override the requested method (should not be used!).
+ # * :url override the URL used in the request.
+ # * :payload override the document or data sent in the message body (only PUT or POST).
+ # * :headers any additional headers (overrides :content_type and :accept)
+ # * :timeout and :open_timeout the time in miliseconds to wait for the request, see RestClient API for more details.
+ # * :verify_ssl, :ssl_client_cert, :ssl_client_key, and :ssl_ca_file, SSL handling methods.
+ #
+ #
+ # Any other remaining options will be sent to the MultiJSON backend except for the :raw option.
+ #
+ # When :raw is true in PUT and POST requests, no attempt will be made to convert the document payload to JSON. This is
+ # not normally necessary as IO and Tempfile objects will not be parsed anyway. The result of the request will
+ # *always* be parsed.
+ #
+ # For all other requests, mainly GET, the :raw option will make no attempt to parse the result. This
+ # is useful for receiving files from the database.
+ #
+
module RestAPI
+ # Send a GET request.
+ def get(uri, options = {})
+ execute(uri, :get, options)
+ end
+
+ # Send a PUT request.
+ def put(uri, doc = nil, options = {})
+ execute(uri, :put, options, doc)
+ end
+
+ # Send a POST request.
+ def post(uri, doc = nil, options = {})
+ execute(uri, :post, options, doc)
+ end
+
+ # Send a DELETE request.
+ def delete(uri, options = {})
+ execute(uri, :delete, options)
+ end
+
+ # Send a COPY request to the URI provided.
+ def copy(uri, destination, options = {})
+ opts = options.nil? ? {} : options.dup
+ # also copy headers!
+ opts[:headers] = options[:headers].nil? ? {} : options[:headers].dup
+ opts[:headers]['Destination'] = destination
+ execute(uri, :copy, opts)
+ end
+
+ # The default RestClient headers used in each request.
def default_headers
{
:content_type => :json,
:accept => :json
}
end
- def get(uri, options = {})
- begin
- parse_response(RestClient.get(uri, default_headers), options.dup)
- rescue => e
- if $DEBUG
- raise "Error while sending a GET request #{uri}\n: #{e}"
- else
- raise e
- end
- end
- end
+ private
- def put(uri, doc = nil, options = {})
- opts = options.dup
- payload = payload_from_doc(doc, opts)
+ # Perform the RestClient request by removing the parse specific options, ensuring the
+ # payload is prepared, and sending the request ready to parse the response.
+ def execute(url, method, options = {}, payload = nil)
+ request, parser = prepare_and_split_options(url, method, options)
+ # Prepare the payload if it is provided
+ request[:payload] = payload_from_doc(payload, parser) if payload
begin
- parse_response(RestClient.put(uri, payload, default_headers.merge(opts)), opts)
+ parse_response(RestClient::Request.execute(request), parser)
rescue Exception => e
if $DEBUG
- raise "Error while sending a PUT request #{uri}\npayload: #{payload.inspect}\n#{e}"
+ raise "Error while sending a #{method.to_s.upcase} request #{uri}\noptions: #{opts.inspect}\n#{e}"
else
raise e
end
end
end
- def post(uri, doc = nil, options = {})
- opts = options.dup
- payload = payload_from_doc(doc, opts)
- begin
- parse_response(RestClient.post(uri, payload, default_headers.merge(opts)), opts)
- rescue Exception => e
- if $DEBUG
- raise "Error while sending a POST request #{uri}\npayload: #{payload.inspect}\n#{e}"
+ # Prepare a two hashes, one for the request to the REST backend and a second
+ # for the JSON parser.
+ #
+ # Returns and array of request options and parser options.
+ #
+ def prepare_and_split_options(url, method, options)
+ request = {
+ :url => url,
+ :method => method,
+ :headers => default_headers.merge(options[:headers] || {})
+ }
+ parser = {
+ :raw => false
+ }
+ # Split the options
+ (options || {}).each do |k,v|
+ k = k.to_sym
+ next if k == :headers # already dealt with
+ if restclient_option_keys.include?(k)
+ request[k] = v
+ elsif header_option_keys.include?(k)
+ request[:headers][k] = v
else
- raise e
+ parser[k] = v
end
end
+ [request, parser]
end
- def delete(uri, options = {})
- parse_response(RestClient.delete(uri, default_headers), options.dup)
- end
-
- def copy(uri, destination, options = {})
- parse_response(RestClient::Request.execute( :method => :copy,
- :url => uri,
- :headers => default_headers.merge('Destination' => destination)
- ).to_s, options)
- end
-
- protected
-
# Check if the provided doc is nil or special IO device or temp file. If not,
# encode it into a string.
+ #
+ # The options supported are:
+ # * :raw TrueClass, if true the payload will not be altered.
+ #
def payload_from_doc(doc, opts = {})
(opts.delete(:raw) || doc.nil? || doc.is_a?(IO) || doc.is_a?(Tempfile)) ? doc : MultiJson.encode(doc)
end
@@ -73,5 +140,16 @@ def parse_response(result, opts = {})
opts.delete(:raw) ? result : MultiJson.decode(result, opts.update(:max_nesting => false))
end
+ # An array of all the options that should be passed through to restclient.
+ # Anything not in this list will be passed to the JSON parser.
+ def restclient_option_keys
+ [:method, :url, :payload, :headers, :timeout, :open_timeout,
+ :verify_ssl, :ssl_client_cert, :ssl_client_key, :ssl_ca_file]
+ end
+
+ def header_option_keys
+ [ :content_type, :accept ]
+ end
+
end
end
View
@@ -1,3 +1,3 @@
module CouchRest
- VERSION = '1.1.0.pre3'
+ VERSION = '1.1.0'
end
@@ -25,7 +25,7 @@
@cr.info.class.should == Hash
end
end
-
+
it "should restart" do
@cr.restart!
begin
@@ -303,7 +303,7 @@
@db.fetch_attachment(@db.get('mydocwithattachment'), 'test.html').should == @attach
end
end
-
+
describe "PUT attachment from file" do
before(:each) do
filename = FIXTURE_PATH + '/attachments/couchdb.png'
@@ -316,12 +316,11 @@
r = @db.put_attachment({'_id' => 'attach-this'}, 'couchdb.png', image = @file.read, {:content_type => 'image/png'})
r['ok'].should == true
doc = @db.get("attach-this")
- attachment = @db.fetch_attachment(doc,"couchdb.png")
- if attachment.respond_to?(:net_http_res)
- attachment.net_http_res.body.should == image
- else
- attachment.should == image
- end
+ attachment = @db.fetch_attachment(doc, "couchdb.png")
+ (attachment == image).should be_true
+ #if attachment.respond_to?(:net_http_res)
+ # attachment.net_http_res.body.should == image
+ #end
end
end
Oops, something went wrong.

0 comments on commit 974d283

Please sign in to comment.