Suprise, suprise...look whose coding!
Yes, my Cuban bee's after finally hitting my limitations with the CouchDB 0.7.2 adapter I took one for the team and got it working for 0.8.0.
This is by no means complete as I am going to build paging into the adapter as well before I submit it for usage in DataMapper. So without any further delay...Here are the goodies. I'll again just drop in what I've overloaded so you can have a pretty seamless transition if you need it. Also, this is currently running off of the CouchDB 0.8.0 trunk (see this page for subversion linkage)
Also, note the TODO, it should be done later this evening all things considered. Last but not least, I hope this helps someone other than myself, enjoy!
module DataMapper
module Types
class Object < DataMapper::Type
primitive String
size 65535
lazy true
track :hash
def self.dump(value, property)
value.to_json unless value.nil?
end
def self.load(value, property)
value.nil? ? nil : value
end
end
end
end
module DataMapper
class Collection < LazyArray
attr_reader :_total_rows
private
def initialize(query, options = {}, &block)
assert_kind_of 'query', query, Query
unless block_given?
raise ArgumentError, 'a block must be supplied for lazy loading results', caller
end
@query = query
@key_properties = model.key(repository.name)
@_total_rows = options[:total_rows] || 0
super()
load_with(&block)
end
end
end
module DataMapper
module Adapters
class CouchDBAdapter < AbstractAdapter
def read_one(query)
doc = request do |http|
http.request(build_request(query))
end
unless doc["total_rows"] == 0
data = doc['rows'].first
query.model.load(
query.fields.map do |property|
data["value"][property.field.to_s]
end,
query)
end
end
def read_many(query)
doc = request do |http|
http.request(build_request(query))
end
Collection.new(query, {:total_rows => doc['total_rows']}) do |collection|
doc['rows'].each do |doc|
collection.load(
query.fields.map do |property|
property.typecast(doc["value"][property.field.to_s])
end
)
end
end
end
def ad_hoc_request(query)
if query.order.empty?
key = "null"
else
key = (query.order.map do |order|
"doc.#{order.property.field}"
end).join(", ")
key = "[#{key}]"
end
options = []
options << "count=#{query.limit}" if query.limit
options << "skip=#{query.offset}" if query.offset
options = options.empty? ? nil : "?#{options.join('&')}"
request = Net::HTTP::Post.new("/#{self.escaped_db_name}/_temp_view#{options}")
request["Content-Type"] = "application/json"
if query.conditions.empty?
request.body = '{"language":"javascript","map":"function(doc) { if (doc.type == \'' + query.model.name.downcase + '\') { emit(' + key + ', doc);} }"}'
else
conditions = query.conditions.map do |operator, property, value|
condition = "doc.#{property.field}"
condition << case operator
when :eql then " == '#{value}'"
when :not then " != '#{value}'"
when :gt then " > #{value}"
when :gte then " >= #{value}"
when :lt then " < #{value}"
when :lte then " <= #{value}"
when :like then like_operator(value)
end
end
request.body = '{"language":"javascript","map":"function(doc) {if (doc.type == \'' + query.model.name.downcase + '\') { if (' + conditions.join(' && ') + ') { emit(' + key + ', doc);}}}"}'
end
request
end
def request(parse_result = true, &block)
res = nil
Net::HTTP.start(@uri.host, @uri.port) do |http|
res = yield(http)
end
# debugger
JSON.parse(res.body) if parse_result
end
module Migration
def create_model_storage(repository, model)
assert_kind_of 'repository', repository, Repository
assert_kind_of 'model', model, Resource::ClassMethods
uri = "/#{self.escaped_db_name}/_design/#{model.storage_name(self.name)}"
view = Net::HTTP::Put.new(uri)
view['content_type'] = "javascript"
views = model.views.reject {|key, value| value.nil?}
# TODO: This absolutely should be handled up a level.
# You shouuld pass view a hash
#{:map => "function(doc){...}", :reduce => "function(doc){...}"}
# We'll get there...
view.body = { :views => views.each {|k,v| views[k] = {:map => v}} }.to_json
request do |http|
http.request(view)
end
end
def destroy_model_storage(repository, model)
assert_kind_of 'repository', repository, Repository
assert_kind_of 'model', model, Resource::ClassMethods
uri = "/#{self.escaped_db_name}/_design/#{model.storage_name(self.name)}"
response = http_get(uri)
unless response['error']
uri += "?rev=#{response["_rev"]}"
http_delete(uri)
end
end
end
end
end
end