diff --git a/README.md b/README.md index e5f0940..6e9cabe 100644 --- a/README.md +++ b/README.md @@ -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: diff --git a/lib/dolly/collection.rb b/lib/dolly/collection.rb index 3dc7dfa..6df226a 100644 --- a/lib/dolly/collection.rb +++ b/lib/dolly/collection.rb @@ -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 @@ -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 @@ -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 = {} @@ -81,7 +70,7 @@ def doc_class id end def json - @json + @json ||= @collection.to_json end end diff --git a/lib/dolly/document.rb b/lib/dolly/document.rb index 082bc4e..747ce2e 100644 --- a/lib/dolly/document.rb +++ b/lib/dolly/document.rb @@ -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 @@ -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 diff --git a/lib/dolly/query.rb b/lib/dolly/query.rb index 8ae6a6e..cf92062 100644 --- a/lib/dolly/query.rb +++ b/lib/dolly/query.rb @@ -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" @@ -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 = {} @@ -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 diff --git a/lib/dolly/request.rb b/lib/dolly/request.rb index 93ea44a..2e49ea5 100644 --- a/lib/dolly/request.rb +++ b/lib/dolly/request.rb @@ -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? @@ -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 diff --git a/lib/tasks/db.rake b/lib/tasks/db.rake index 5dde5a9..3c0947d 100644 --- a/lib/tasks/db.rake +++ b/lib/tasks/db.rake @@ -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') diff --git a/test/collection_test.rb b/test/collection_test.rb index 94c646b..e9973aa 100644 --- a/test/collection_test.rb +++ b/test/collection_test.rb @@ -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 @@ -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) diff --git a/test/document_test.rb b/test/document_test.rb index 720116f..1a4678d 100644 --- a/test/document_test.rb +++ b/test/document_test.rb @@ -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'} @@ -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 @@ -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 @@ -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 @@ -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) diff --git a/test/test_helper.rb b/test/test_helper.rb index 052a116..d4de038 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -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