Skip to content

Commit

Permalink
Getting basic working code base with new connection model, requires m…
Browse files Browse the repository at this point in the history
…ore work on tests
  • Loading branch information
samlown committed Jul 9, 2015
1 parent d863dd0 commit 032775e
Show file tree
Hide file tree
Showing 17 changed files with 280 additions and 394 deletions.
4 changes: 2 additions & 2 deletions lib/couchrest.rb
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,9 @@ def parse url
}
end

# set proxy to use
# Set default proxy to use in connections
def proxy url
RestClient.proxy = url
CouchRest::Connection.proxy = url
end

# ensure that a database exists
Expand Down
30 changes: 13 additions & 17 deletions lib/couchrest/connection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -94,12 +94,6 @@ def head(path, options = {})
execute('HEAD', path, options)
end

# Close the connection. This will happen automatically if the current thread is
# killed, so shouldn't be used under normal circumstances.
def close
http.reset
end

protected

# Duplicate and remove excess baggage from the provided URI
Expand All @@ -114,19 +108,14 @@ def clean_uri(uri)
# Take a look at the options povided and try to apply them to the HTTP conneciton.
# We try to maintain RestClient compatability as this is what we used before.
def prepare_http_connection(opts)
@http = HTTPClient.new
@http = HTTPClient.new(opts[:proxy] || self.class.proxy)

# SSL Certificate option mapping
if opts.include?(:verify_ssl)
http.ssl_config.verify_mode = opts[:verify_ssl] ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
end
if opts.include?(:ssl_client_cert)
http.ssl_config.set_client_cert_file(
opts[:ssl_client_cert],
opts[:ssl_client_key],
opts[:ssl_client_key_pass]
)
end
http.ssl_config.client_cert = opts[:ssl_client_cert] if opts.include?(:ssl_client_cert)
http.ssl_config.client_key = opts[:ssl_client_key] if opts.include?(:ssl_client_key)

# Timeout options
http.receive_timeout = opts[:timeout] if opts.include?(:timeout)
Expand Down Expand Up @@ -158,7 +147,7 @@ def execute(method, path, options, payload = nil, &block)

def send_and_parse_response(req, options, &block)
if block_given?
parser = CouchRest::StreamRowParser.new
parser = CouchRest::StreamRowParser.new(options[:continuous] ? :feed : :array)
response = send_request(req) do |chunk|
parser.parse(chunk) do |doc|
block.call(parse_body(doc, options))
Expand Down Expand Up @@ -198,9 +187,9 @@ def parse_body(body, opts)
# * :raw TrueClass, if true the payload will not be altered.
#
def payload_from_doc(req, doc, opts = {})
if doc.is_a?(IO) || doc.is_a?(Tempfile)
if doc.is_a?(IO) || doc.is_a?(StringIO) || doc.is_a?(Tempfile) # attachments
req[:header]['Content-Type'] = mime_for(req[:uri].path)
doc.read
doc
elsif opts[:raw] || doc.nil?
doc
else
Expand Down Expand Up @@ -251,5 +240,12 @@ def convert_content_type(type)
end
end

class << self

# Default proxy URL to use in all connections.
attr_accessor :proxy

end

end
end
47 changes: 22 additions & 25 deletions lib/couchrest/database.rb
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ def save_doc(doc, bulk = false, batch = false)
if doc['_attachments']
doc['_attachments'] = encode_attachments(doc['_attachments'])
end

if bulk
@bulk_save_cache << doc
bulk_save if @bulk_save_cache.length >= @bulk_save_cache_limit
Expand All @@ -149,12 +150,8 @@ def save_doc(doc, bulk = false, batch = false)
connection.put "#{path}/#{slug}", doc
end
else
begin
slug = doc['_id'] = @server.next_uuid
connection.put "#{path}/#{slug}", doc
rescue #old version of couchdb
connection.post path, doc
end
slug = doc['_id'] = @server.next_uuid
connection.put "#{path}/#{slug}", doc
end
if result['ok']
doc['_id'] = result['id']
Expand Down Expand Up @@ -226,7 +223,7 @@ def copy_doc(doc, dest)
else
dest
end
connection.copy "#{path}/#{slug}", "#{path}/#{destination}"
connection.copy "#{path}/#{slug}", destination
end

# Updates the given doc by yielding the current state of the doc
Expand Down Expand Up @@ -262,15 +259,21 @@ def update_doc(doc_id, params = {}, update_limit = 10)
# Query a CouchDB view as defined by a <tt>_design</tt> document. Accepts
# paramaters as described in http://wiki.apache.org/couchdb/HttpViewApi
def view(name, params = {}, payload = {}, &block)
opts = {}
params = params.dup
payload['keys'] = params.delete(:keys) if params[:keys]

# Continuous feeds need to be parsed differently
opts[:continuous] = true if params['feed'] == 'continuous'

# Try recognising the name, otherwise assume already prepared
view_path = name_to_view_path(name)
req_path = CouchRest.paramify_url("#{path}/#{view_path}", params)

if payload.empty?
connection.get req_path, {}, &block
connection.get req_path, opts, &block
else
connection.post req_path, payload, {}, &block
connection.post req_path, payload, opts, &block
end
end

Expand Down Expand Up @@ -320,9 +323,11 @@ def fetch_attachment(doc, name)
connection.get path_for_attachment(doc, name), :raw => true
end

# PUT an attachment directly to CouchDB
# PUT an attachment directly to CouchDB, expects an IO object, or a string
# that will be converted to a StringIO in the 'file' parameter.
def put_attachment(doc, name, file, options = {})
connection.put path_for_attachment(doc, name), file, options.merge(:raw => true)
file = StringIO.new(file) if file.is_a?(String)
connection.put path_for_attachment(doc, name), file, options
end

# DELETE an attachment directly from CouchDB
Expand Down Expand Up @@ -350,29 +355,21 @@ def replicate(other_db, continuous, options)
doc_ids = options.delete(:doc_ids)
payload = options
if options.has_key?(:target)
payload[:source] = other_db.root
payload[:source] = other_db.root.to_s
else
payload[:target] = other_db.root
payload[:target] = other_db.root.to_s
end
payload[:continuous] = continuous
payload[:doc_ids] = doc_ids if doc_ids

# Use a short lived request here
CouchRest.post "#{@host}/_replicate", payload
connection.post "_replicate", payload
end

def path_for_attachment(doc, name)
if doc.is_a?(String)
puts "CouchRest::Database#fetch_attachment will eventually require a doc as the first argument, not a doc.id"
docid = doc
rev = nil
else
docid = doc['_id']
rev = doc['_rev']
end
docid = escape_docid(docid)
name = CGI.escape(name)
rev = "?rev=#{doc['_rev']}" if rev
docid = escape_docid(doc['_id'])
name = CGI.escape(name)
rev = doc['_rev'] ? "?rev=#{doc['_rev']}" : ''
"#{path}/#{docid}/#{name}#{rev}"
end

Expand Down
29 changes: 23 additions & 6 deletions lib/couchrest/helper/stream_row_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,30 @@ class StreamRowParser
# String containing the fields provided before and after the rows.
attr_accessor :header

def initialize
# The row level at which we expect to receive "rows" of data.
# Typically this will be 0 for contious feeds, and 1 for most other users.
attr_reader :row_level

# Instantiate a new StreamRowParser with the mode set according to the type of data.
# The supported modes are:
#
# * `:array` - objects are contianed in a data array, the default.
# * `:feed` - each row of the stream is an object, like in continuous changes feeds.
#
def initialize(mode = :array)
@header = ""
@data = ""
@string = false
@escape = false

@row_level = mode == :array ? 1 : 0
@in_rows = false
@obj_level = 0
@obj_close = false
end

def parse(segment, &block)
@in_rows = true if @row_level == 0
segment.each_char do |c|
if @string
# Inside a string, handling escaping and closure
Expand All @@ -41,10 +53,11 @@ def parse(segment, &block)
end
end
else
# Inside an object
@obj_close = false
if @obj_level == 1 && c == "[" # start of rows
if @obj_level == @row_level && c == "[" # start of rows
@in_rows = true
elsif @obj_level == 1 && c == "]" # end of rows
elsif @obj_level == @row_level && c == "]" # end of rows
@in_rows = false
elsif c == "{" # object
@obj_level += 1
Expand All @@ -57,14 +70,18 @@ def parse(segment, &block)
end

# Append data
if @obj_level == 0 || (@obj_level == 1 && !@obj_close)
@header << c unless @in_rows && (c == ',' || c == ' ' || c == "\n") # skip row whitespace
if @row_level > 0
if @obj_level == 0 || (@obj_level == @row_level && !@obj_close)
@header << c unless @in_rows && (c == ',' || c == ' ' || c == "\n") # skip row whitespace
else
@data << c
end
else
@data << c
end

# Determine if we need to trigger an event
if @obj_close && @obj_level == 1
if @obj_close && @obj_level == @row_level
block.call(@data)
@data = ""
end
Expand Down
18 changes: 8 additions & 10 deletions lib/couchrest/rest_api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,53 +4,51 @@ module CouchRest
# CouchRest RestAPI
#
# Backwards compatible wrapper for instantiating a connection and performing
# a request on demand.
#
# We recommend using `Server#connection` object directly if possible instead
# of these class methods.
# a request on demand. Useful for quick requests, but not recommended for general
# usage due the extra overhead of establishing a connection.
#

module RestAPI

# Send a GET request.
def get(url, options = {})
connection(url, options) do |uri, conn|
conn.get(uri.path, options)
conn.get(uri.request_uri, options)
end
end

# Send a PUT request.
def put(url, doc = nil, options = {})
connection(url, options) do |uri, conn|
conn.put(uri.path, doc, options)
conn.put(uri.request_uri, doc, options)
end
end

# Send a POST request.
def post(url, doc = nil, options = {})
connection(url, options) do |uri, conn|
conn.post(uri.path, doc, options)
conn.post(uri.request_uri, doc, options)
end
end

# Send a DELETE request.
def delete(url, options = {})
connection(url, options) do |uri, conn|
conn.delete(uri.path, options)
conn.delete(uri.request_uri, options)
end
end

# Send a COPY request to the URI provided.
def copy(url, destination, options = {})
connection(url, options) do |uri, conn|
conn.copy(uri.path, destination, options)
conn.copy(uri.request_uri, destination, options)
end
end

# Send a HEAD request.
def head(url, options = {})
connection(url, options) do |uri, conn|
conn.head(uri.path, options)
conn.head(uri.request_uri, options)
end
end

Expand Down
8 changes: 5 additions & 3 deletions lib/couchrest/server.rb
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
module CouchRest
class Server

##
# URI object of the link to the server we're using.
attr_reader :uri

##
# Number of UUIDs to fetch from the server when preparing to save new
# documents. Set to 1000 by default.
attr_reader :uuid_batch_count

# Accessor for the current internal array of UUIDs ready to be used when
# saving new documents. See also #next_uuid.
attr_reader :uuids

def initialize(server = 'http://127.0.0.1:5984', uuid_batch_count = 1000)
@uri = prepare_uri(server).freeze
@uuid_batch_count = uuid_batch_count
Expand Down Expand Up @@ -58,7 +60,7 @@ def restart!
# Retrive an unused UUID from CouchDB. Server instances manage caching a list of unused UUIDs.
def next_uuid(count = @uuid_batch_count)
if uuids.nil? || uuids.empty?
self.uuids = connection.get("_uuids?count=#{count}")["uuids"]
@uuids = connection.get("_uuids?count=#{count}")["uuids"]
end
uuids.pop
end
Expand Down
Loading

0 comments on commit 032775e

Please sign in to comment.