Skip to content

Commit

Permalink
adding ConsistentHash
Browse files Browse the repository at this point in the history
  • Loading branch information
Robert Sosinski committed Oct 29, 2010
1 parent 6d19edf commit e2c679a
Show file tree
Hide file tree
Showing 8 changed files with 125 additions and 46 deletions.
50 changes: 14 additions & 36 deletions lib/couch-client.rb
@@ -1,45 +1,23 @@
$:.unshift(File.dirname(File.expand_path(__FILE__)))

# require '../core_ext/hash'

require 'couch-client/consistent_hash'
require 'couch-client/connection'
require 'couch-client/connection_handler'
require 'couch-client/hookup'
require 'couch-client/database'
require 'couch-client/document'
require 'couch-client/attachment_list'
require 'couch-client/attachment'
require 'couch-client/design'
require 'couch-client/collection'
require 'couch-client/row'

# The CouchClient module is the overall container of all CouchClient logic.
module CouchClient
VERSION = "0.0.1"

begin
# Require HashWithIndifferentAccess gem if available.
require 'active_support/hash_with_indifferent_access'
class Hash < ActiveSupport::HashWithIndifferentAccess
private
# Patching `convert_value` method, else it will absorb all "Hashlike"
# objects and convert them into a HashWithIndifferentAccess.
# NOTE: As this is patching a private method, this is probably not a good idea.
def convert_value(value)
if value.instance_of?(::Hash) # specifying Ruby's Hash, not CouchClient's Hash
self.class.new_from_hash_copying_default(value)
elsif value.instance_of?(Array)
value.collect { |e| e.instance_of?(::Hash) ? self.class.new_from_hash_copying_default(e) : e }
else
value
end
end
end
rescue LoadError
# If HashWithIndifferentAccess is not available, use Hash.
class Hash < ::Hash; end
end

# requiring libraries inside of the CouchClient library so they can use
# HashWithIndifferentAccess instead of Hash if it is available.
require 'couch-client/connection'
require 'couch-client/connection_handler'
require 'couch-client/hookup'
require 'couch-client/database'
require 'couch-client/document'
require 'couch-client/attachment_list'
require 'couch-client/attachment'
require 'couch-client/design'
require 'couch-client/collection'
require 'couch-client/row'

class Error < Exception; end

# Start using CouchClient by constructing a new CouchClient::Connection object with a Hash:
Expand Down
2 changes: 1 addition & 1 deletion lib/couch-client/attachment.rb
@@ -1,7 +1,7 @@
module CouchClient
# The Attachment is an extended Hash that provides additional methods to
# interact with attached files saved within a document.
class Attachment < Hash
class Attachment < ConsistentHash
attr_reader :name

# Attachment is constructed the id of the document it is attached to,
Expand Down
10 changes: 4 additions & 6 deletions lib/couch-client/attachment_list.rb
@@ -1,10 +1,8 @@
module CouchClient
# The sole purpose of Attachment List is to prevent ActiveSupport::HashWithIndifferentAccess
# from absorbing instances of Attachment and making them a HashWithIndifferentAccess. This
# is neccessary as the previous patch to HashWithIndifferentAccess will only prevent an
# object being absorbed if it is not also nested within another HashWithIndifferentAccess.
class AttachmentList < Hash
# AttachmentList is constructed with the hash of _attachments.
# The AttachmentList prevents ConsistentHash from absorbing
# instances of Attachment and making them a ConsistentHash.
class AttachmentList < ConsistentHash
# AttachmentList is constructed with a hash of attachments.
def initialize(attachments)
self.merge!(attachments)
end
Expand Down
2 changes: 1 addition & 1 deletion lib/couch-client/connection_handler.rb
Expand Up @@ -50,7 +50,7 @@ def path(path_obj = nil, query_obj = nil)
raise InvalidPathObject.new("path must be of type 'Array' not of type '#{path_obj.class}'")
end

query_str = if query_obj.is_a?(::Hash)
query_str = if query_obj.is_a?(Hash)
# If a Hash, stringify and escape each object, join each key/value with a "=" and each pair with a "&"
query_obj.to_a.map{|q| q.map{|r| CGI.escape(r.to_s)}.join("=")}.join("&")
else
Expand Down
96 changes: 96 additions & 0 deletions lib/couch-client/consistent_hash.rb
@@ -0,0 +1,96 @@
module CouchClient
# ConsistentHash allows indifferent access with either with symbols or strings
# while also converting symbol values in Arrays or Hashes into strings values.
#
# This code was is heavily influenced by ActiveSupport::HashWithIndifferentAccess.
class ConsistentHash < Hash
def initialize(constructor = {})
if constructor.is_a?(Hash)
super
update(constructor)
else
super(constructor)
end
end

def default(key = nil)
if key.is_a?(Symbol) && include?(key = key.to_s)
self[key]
else
super
end
end

def self.new_from_hash_copying_default(hash)
ConsistentHash.new(hash).tap do |new_hash|
new_hash.default = hash.default
end
end

alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
alias_method :regular_update, :update unless method_defined?(:regular_update)

def []=(key, value)
regular_writer(convert_key(key), convert_value(value))
end

def update(other_hash)
other_hash.each_pair do |key, value|
regular_writer(convert_key(key), convert_value(value))
end
self
end

alias_method :merge!, :update

def key?(key)
super(convert_key(key))
end

alias_method :include?, :key?
alias_method :has_key?, :key?
alias_method :member?, :key?

def fetch(key, *extras)
super(convert_key(key), *extras)
end

def values_at(*indices)
indices.collect {|key| self[convert_key(key)]}
end

def dup
ConsistentHash.new(self)
end

def merge(hash)
dup.update(hash)
end

def delete(key)
super(convert_key(key))
end

def to_hash
Hash.new(default).merge!(self)
end

protected

def convert_key(key)
key.is_a?(Symbol) ? key.to_s : key
end

def convert_value(value)
if value.instance_of?(Hash)
self.class.new_from_hash_copying_default(value)
elsif value.instance_of?(Symbol)
value.to_s
elsif value.instance_of?(Array)
value.collect{|e| convert_value(e)}
else
value
end
end
end
end
2 changes: 1 addition & 1 deletion lib/couch-client/document.rb
Expand Up @@ -5,7 +5,7 @@ class DocumentNotAvailable < Exception; end

# The Document is an extended Hash that provides additional methods to
# save, update (with attachments), and delete documents on the CouchDB.
class Document < Hash
class Document < ConsistentHash
attr_reader :code, :error

# Document is constructed with a status code, response body,
Expand Down
2 changes: 1 addition & 1 deletion lib/couch-client/row.rb
@@ -1,7 +1,7 @@
module CouchClient
# The Row is an extended Hash that provides additional state to
# get status codes and connect documents to the server.
class Row < Hash
class Row < ConsistentHash
def initialize(code, row, connection)
self.merge!(row)
self["doc"] &&= Document.new(code, row["doc"], connection)
Expand Down
7 changes: 7 additions & 0 deletions spec/attachment_list_spec.rb
@@ -0,0 +1,7 @@
require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")

describe CouchClient::AttachmentList do
it 'should exist inherent from ConsistentHash' do
CouchClient::AttachmentList.ancestors.should include(CouchClient::ConsistentHash)
end
end

0 comments on commit e2c679a

Please sign in to comment.