Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,23 @@

Dolly is a Object Oriented CouchDB interface to interact with the JSON documents through CouchDB's RESTful API.

## Versions

### Dolly 1.x

It is only compatible with CouchDB 1.x

### Dolly 2.x

Dolly 2.x is compatible only with CouchDB 2.x and up

#### Braking changes

* Due to fix on HTTParty, now `parsed_response` returns a `Hash` instead of a `JSON` string.
So no need to call `JSON.parse` on db responses.
* CochDB 2.0 will not accept post requests to views without a rquest body.
* No need to call `to_json` on body values for requests

## Installation

Add this line to your application's Gemfile:
Expand Down
29 changes: 9 additions & 20 deletions lib/dolly/collection.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
module Dolly
class Collection < DelegateClass(Set)
attr_accessor :rows
attr_accessor :rows, :collection
attr_writer :json, :docs_class

def initialize str, docs_class
def initialize collection, docs_class
@docs_class = docs_class
@json = str
@collection = collection
initial = []
super(initial)
load
Expand All @@ -17,23 +17,13 @@ def last

def update_properties! properties ={}
properties.each do |key, value|

regex = %r{
\"#{key}\": # find key definition in json string
( # start value group
\"[^\"]*\" # find anything (even empty) between \" and \"
| # logical OR
null #literal null value
) # end value group
}x

raise Dolly::MissingPropertyError unless json.match regex
json.gsub! regex, "\"#{key}\":\"#{value}\""
each do |doc|
raise Dolly::MissingPropertyError unless doc.respond_to? key.to_sym
doc.send(:"#{key}=", value)
end
end

BulkDocument.new(Dolly::Document.database, to_a).save
clear
load
self
end

Expand All @@ -57,8 +47,7 @@ def rows= ary
end

def load
parsed = JSON::parse json
self.rows = parsed['rows']
self.rows = collection['rows']
end

def to_json options = {}
Expand All @@ -81,7 +70,7 @@ def doc_class id
end

def json
@json
@json ||= @collection.to_json
end

end
Expand Down
4 changes: 2 additions & 2 deletions lib/dolly/document.rb
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def save options = {}
set_created_at if timestamps[self.class.name]
set_updated_at if timestamps[self.class.name]
response = database.put(id_as_resource, self.doc.to_json)
obj = JSON::parse response.parsed_response
obj = response.parsed_response
doc['_rev'] = obj['rev'] if obj['rev']
obj['ok']
end
Expand All @@ -78,7 +78,7 @@ def destroy hard = true
if hard
q = id_as_resource + "?rev=#{rev}"
response = database.delete(q)
JSON::parse response.parsed_response
response.parsed_response
else
self.doc['_deleted'] = true
self.save
Expand Down
6 changes: 3 additions & 3 deletions lib/dolly/query.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def find *keys
if keys.count > 1
build_collection( query_hash )
else
self.new.from_json( database.all_docs(query_hash).parsed_response )
self.new( database.all_docs(query_hash) )
end
rescue NoMethodError => err
if err.message == "undefined method `[]' for nil:NilClass"
Expand Down Expand Up @@ -48,7 +48,7 @@ def last limit = 1

def build_collection q
res = database.all_docs(q)
Collection.new res.parsed_response, name_for_class
Collection.new res, name_for_class
end

def find_with doc, view_name, opts = {}
Expand All @@ -72,7 +72,7 @@ def view doc, options = {}
end

def raw_view doc, view, opts = {}
JSON.parse database.get "_design/#{doc}/_view/#{view}", opts
database.get "_design/#{doc}/_view/#{view}", opts
end

end
Expand Down
9 changes: 5 additions & 4 deletions lib/dolly/request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,14 @@ def uuids opts = {}
end

def all_docs data = {}
data = values_to_json data.merge( include_docs: true )
request :get, full_path('_all_docs'), {query: data}
data = values_to_json data.merge( include_docs: true )
request(:get, full_path('_all_docs'), { query: data }).parsed_response
end

def request method, resource, data = nil
data ||= {}
data.merge!(basic_auth: auth_info) if auth_info.present?
headers = { 'Content-Type' => 'application/json' }
headers = { 'Content-Type' => 'application/json', 'Accept' => "application/json" }
headers.merge! data[:headers] if data[:headers]
response = self.class.send method, resource, data.merge(headers: headers)
log_request(resource, response.code) if Dolly.log_requests?
Expand All @@ -81,11 +81,12 @@ def request method, resource, data = nil
end

private

def tools path, opts = nil
data = {}
q = "?#{CGI.unescape(opts.to_query)}" unless opts.blank?
data.merge!(basic_auth: auth_info) if auth_info.present?
JSON::parse self.class.get("/#{path}#{q}", data)
self.class.get("/#{path}#{q}", data)
end

def auth_info
Expand Down
2 changes: 1 addition & 1 deletion lib/tasks/db.rake
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ namespace :db do
view_doc.merge!( '_id' => design_doc_name, 'language' => 'coffeescript')

begin
hash_doc = JSON::parse Dolly::Document.database.get(view_doc["_id"]).parsed_response
hash_doc = Dolly::Document.database.get(view_doc["_id"]).parsed_response

rev = hash_doc.delete('_rev')

Expand Down
4 changes: 2 additions & 2 deletions test/collection_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class CollectionTest < ActiveSupport::TestCase

def setup
@json = '{"total_rows":2,"offset":0,"rows":[{"id":"foo_bar/0","key":"foo_bar","value":1,"doc":{"_id":"foo_bar/0","_rev":"7f66379ac92eb6dfafa50c94bd795122","foo":"Foo B","bar":"Bar B","type":"foo_bar"}},{"id":"foo_bar/1","key":"foo_bar","value":1,"doc":{"_id":"foo_bar/1","_rev":"4d33cea0e55142c9ecc6a81600095469","foo":"Foo A","bar":"Bar A","type":"foo_bar"}}]}'
@collection = Dolly::Collection.new @json, FooBar
@collection = Dolly::Collection.new JSON.parse(@json), FooBar
end

test 'each returns nil' do
Expand Down Expand Up @@ -44,7 +44,7 @@ def setup

test 'update empty attributes' do
json = '{"total_rows":2,"offset":0,"rows":[{"id":"foo_bar/0","key":"foo_bar","value":1,"doc":{"_id":"foo_bar/0","_rev":"7f66379ac92eb6dfafa50c94bd795122","foo":null,"bar":"","type":"foo_bar"}},{"id":"foo_bar/1","key":"foo_bar","value":1,"doc":{"_id":"foo_bar/1","_rev":"4d33cea0e55142c9ecc6a81600095469","foo":"Foo A","bar":"Bar A","type":"foo_bar"}}]}'
collection = Dolly::Collection.new json, FooBar
collection = Dolly::Collection.new JSON.parse(json), FooBar

collection.update_properties! foo: 'Foo 4 All', bar: 'stuff'
assert_equal ['Foo 4 All', 'Foo 4 All'], collection.map(&:foo)
Expand Down
34 changes: 19 additions & 15 deletions test/document_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ class Bar < FooBar
class DocumentTest < ActiveSupport::TestCase
DB_BASE_PATH = "http://localhost:5984/test".freeze

attr_accessor :headers

def setup
data = {foo: 'Foo', bar: 'Bar', type: 'foo_bar'}

Expand All @@ -71,18 +73,20 @@ def setup
build_request [["foo_bar","2"]], empty_resp
build_request [["foo_bar","1"],["foo_bar","2"]], @multi_resp

@headers = { content_type: 'application/json', accept: 'application/json' }

#TODO: Mock Dolly::Request to return helper with expected response. request builder can be tested by itself.
FakeWeb.register_uri :get, "#{query_base_path}?startkey=%22foo_bar%2F%22&endkey=%22foo_bar%2F%EF%BF%B0%22&include_docs=true", body: @multi_resp.to_json
FakeWeb.register_uri :get, "#{query_base_path}?startkey=%22foo_bar%2F%22&endkey=%22foo_bar%2F%EF%BF%B0%22&limit=1&include_docs=true", body: view_resp.to_json
FakeWeb.register_uri :get, "#{query_base_path}?endkey=%22foo_bar%2F%22&startkey=%22foo_bar%2F%EF%BF%B0%22&limit=1&descending=true&include_docs=true", body: view_resp.to_json
FakeWeb.register_uri :get, "#{query_base_path}?startkey=%22foo_bar%2F%22&endkey=%22foo_bar%22%2C%7B%7D&limit=2&include_docs=true", body: @multi_resp.to_json
FakeWeb.register_uri :get, "#{query_base_path}?endkey=%22foo_bar%2F%22&startkey=%22foo_bar%2F%EF%BF%B0%22&limit=2&descending=true&include_docs=true", body: @multi_resp.to_json
FakeWeb.register_uri :get, "#{query_base_path}?keys=%5B%22foo_bar%2F1%22%5D&include_docs=true", body: view_resp.to_json
FakeWeb.register_uri :get, "#{query_base_path}?keys=%5B%5D&include_docs=true", body: not_found_resp.to_json
FakeWeb.register_uri :get, "#{query_base_path}?startkey=%22foo_bar%2F%22&endkey=%22foo_bar%2F%EF%BF%B0%22&include_docs=true", body: @multi_resp.to_json, **headers
FakeWeb.register_uri :get, "#{query_base_path}?startkey=%22foo_bar%2F%22&endkey=%22foo_bar%2F%EF%BF%B0%22&limit=1&include_docs=true", body: view_resp.to_json, **headers
FakeWeb.register_uri :get, "#{query_base_path}?endkey=%22foo_bar%2F%22&startkey=%22foo_bar%2F%EF%BF%B0%22&limit=1&descending=true&include_docs=true", body: view_resp.to_json, **headers
FakeWeb.register_uri :get, "#{query_base_path}?startkey=%22foo_bar%2F%22&endkey=%22foo_bar%22%2C%7B%7D&limit=2&include_docs=true", body: @multi_resp.to_json, **headers
FakeWeb.register_uri :get, "#{query_base_path}?endkey=%22foo_bar%2F%22&startkey=%22foo_bar%2F%EF%BF%B0%22&limit=2&descending=true&include_docs=true", body: @multi_resp.to_json, **headers
FakeWeb.register_uri :get, "#{query_base_path}?keys=%5B%22foo_bar%2F1%22%5D&include_docs=true", body: view_resp.to_json, **headers
FakeWeb.register_uri :get, "#{query_base_path}?keys=%5B%5D&include_docs=true", body: not_found_resp.to_json, **headers
FakeWeb.register_uri :get, "#{query_base_path}?keys=%5B%22foo_bar%2Ferror%22%5D&include_docs=true", body: 'error', status: ["500", "Error"]
FakeWeb.register_uri :get, "#{query_base_path}?keys=%5B%22foo_bar%2F1%22%2C%22foo_bar%2F2%22%5D&include_docs=true", body: @multi_resp.to_json
FakeWeb.register_uri :get, "#{query_base_path}?keys=%5B%22foo_bar%2F2%22%5D&include_docs=true", body: not_found_resp.to_json
FakeWeb.register_uri :get, "#{query_base_path}?keys=%5B%22foo_bar%2Fbig_doc%22%5D&include_docs=true", body: build_view_response([data.merge(other_property: 'other')]).to_json
FakeWeb.register_uri :get, "#{query_base_path}?keys=%5B%22foo_bar%2F1%22%2C%22foo_bar%2F2%22%5D&include_docs=true", body: @multi_resp.to_json, **headers
FakeWeb.register_uri :get, "#{query_base_path}?keys=%5B%22foo_bar%2F2%22%5D&include_docs=true", body: not_found_resp.to_json, **headers
FakeWeb.register_uri :get, "#{query_base_path}?keys=%5B%22foo_bar%2Fbig_doc%22%5D&include_docs=true", body: build_view_response([data.merge(other_property: 'other')]).to_json, **headers
end

test 'new in memory document' do
Expand Down Expand Up @@ -182,7 +186,7 @@ def setup
test 'reload reloads the doc attribute from database' do
assert foo = FooBar.find('1')
expected_doc = foo.doc.dup
FakeWeb.register_uri :get, "#{query_base_path}?keys=%5B%22foo_bar%2F0%22%5D&include_docs=true", body: build_view_response([expected_doc]).to_json
FakeWeb.register_uri :get, "#{query_base_path}?keys=%5B%22foo_bar%2F0%22%5D&include_docs=true", body: build_view_response([expected_doc]).to_json, **headers
assert foo.foo = 1
assert_not_equal expected_doc, foo.doc
assert foo.reload
Expand All @@ -191,12 +195,12 @@ def setup

test 'accessors work as expected after reload' do
resp = {ok: true, id: "foo_bar/1", rev: "FF0000"}
FakeWeb.register_uri :put, "http://localhost:5984/test/foo_bar%2F0", body: resp.to_json
FakeWeb.register_uri :put, "http://localhost:5984/test/foo_bar%2F0", body: resp.to_json, **headers
assert foo = FooBar.find('1')
assert foo.foo = 1
assert foo.save
assert expected_doc = foo.doc
FakeWeb.register_uri :get, "#{query_base_path}?keys=%5B%22foo_bar%2F0%22%5D&include_docs=true", body: build_view_response([expected_doc]).to_json
FakeWeb.register_uri :get, "#{query_base_path}?keys=%5B%22foo_bar%2F0%22%5D&include_docs=true", body: build_view_response([expected_doc]).to_json, **headers
assert foo.reload
assert_equal 1, foo.foo
end
Expand Down Expand Up @@ -270,14 +274,14 @@ def setup
end

test 'query custom view' do
FakeWeb.register_uri :get, "http://localhost:5984/test/_design/test/_view/custom_view?key=1&include_docs=true", body: @multi_resp.to_json
FakeWeb.register_uri :get, "http://localhost:5984/test/_design/test/_view/custom_view?key=1&include_docs=true", body: @multi_resp.to_json, **headers
f = FooBar.find_with "test", "custom_view", key: 1
assert_equal 2, f.count
f.each{ |d| assert d.kind_of?(FooBar) }
end

test 'query custom view collation' do
FakeWeb.register_uri :get, "http://localhost:5984/test/_design/test/_view/custom_view?startkey=%5B1%5D&endkey=%5B1%2C%7B%7D%5D&include_docs=true", body: @multi_type_resp.to_json
FakeWeb.register_uri :get, "http://localhost:5984/test/_design/test/_view/custom_view?startkey=%5B1%5D&endkey=%5B1%2C%7B%7D%5D&include_docs=true", body: @multi_type_resp.to_json, **headers
f = FooBar.find_with "test", "custom_view", { startkey: [1], endkey: [1, {}]}
assert_equal 2, f.count
assert f.first.kind_of?(FooBar)
Expand Down
2 changes: 1 addition & 1 deletion test/test_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def global_setup
FakeWeb.allow_net_connect = false

response = { uuids: [SecureRandom.uuid] }
FakeWeb.register_uri(:get, %r|http://.*:5984/_uuids.*|, body: response.to_json)
FakeWeb.register_uri(:get, %r|http://.*:5984/_uuids.*|, body: response.to_json, content_type: 'application/json', accept: 'application/json')
end

protected
Expand Down