norr-couchrest 0.30.4 → 0.33.01
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +58 -10
- data/history.txt +45 -1
- data/lib/couchrest.rb +9 -49
- data/lib/couchrest/core/adapters/restclient.rb +35 -0
- data/lib/couchrest/core/database.rb +18 -10
- data/lib/couchrest/core/document.rb +0 -4
- data/lib/couchrest/core/http_abstraction.rb +48 -0
- data/lib/couchrest/core/rest_api.rb +49 -0
- data/lib/couchrest/middlewares/logger.rb +263 -0
- data/lib/couchrest/mixins/attachments.rb +2 -2
- data/lib/couchrest/mixins/class_proxy.rb +5 -1
- data/lib/couchrest/mixins/collection.rb +23 -6
- data/lib/couchrest/mixins/design_doc.rb +5 -0
- data/lib/couchrest/mixins/document_queries.rb +33 -4
- data/lib/couchrest/mixins/properties.rb +30 -4
- data/lib/couchrest/mixins/views.rb +5 -13
- data/lib/couchrest/monkeypatches.rb +59 -59
- data/lib/couchrest/more/casted_model.rb +3 -2
- data/lib/couchrest/more/extended_document.rb +18 -1
- data/lib/couchrest/validation/validation_errors.rb +7 -0
- data/spec/couchrest/core/couchrest_spec.rb +2 -2
- data/spec/couchrest/core/database_spec.rb +19 -5
- data/spec/couchrest/core/design_spec.rb +1 -1
- data/spec/couchrest/core/document_spec.rb +1 -1
- data/spec/couchrest/core/server_spec.rb +1 -1
- data/spec/couchrest/helpers/pager_spec.rb +1 -1
- data/spec/couchrest/helpers/streamer_spec.rb +1 -1
- data/spec/couchrest/more/casted_extended_doc_spec.rb +1 -1
- data/spec/couchrest/more/casted_model_spec.rb +1 -1
- data/spec/couchrest/more/extended_doc_attachment_spec.rb +1 -1
- data/spec/couchrest/more/extended_doc_spec.rb +27 -2
- data/spec/couchrest/more/extended_doc_subclass_spec.rb +1 -1
- data/spec/couchrest/more/extended_doc_view_spec.rb +21 -9
- data/spec/couchrest/more/property_spec.rb +50 -1
- data/spec/spec_helper.rb +1 -1
- metadata +9 -2
data/README.md
CHANGED
@@ -12,14 +12,17 @@ Note: CouchRest only support CouchDB 0.9.0 or newer.
|
|
12
12
|
|
13
13
|
## Easy Install
|
14
14
|
|
15
|
-
|
15
|
+
$ sudo gem install couchrest
|
16
|
+
|
17
|
+
Alternatively, you can install from Github:
|
18
|
+
|
19
|
+
$ gem sources -a http://gems.github.com (you only have to do this once)
|
20
|
+
$ sudo gem install couchrest-couchrest
|
16
21
|
|
17
22
|
### Relax, it's RESTful
|
18
23
|
|
19
|
-
|
20
|
-
|
21
|
-
while still giving you more control than Open-URI. I recommend it anytime
|
22
|
-
you’re interfacing with a well-defined web service.
|
24
|
+
CouchRest rests on top of a HTTP abstraction layer using by default Heroku’s excellent REST Client Ruby HTTP wrapper.
|
25
|
+
Other adapters can be added to support more http libraries.
|
23
26
|
|
24
27
|
### Running the Specs
|
25
28
|
|
@@ -27,7 +30,7 @@ The most complete documentation is the spec/ directory. To validate your
|
|
27
30
|
CouchRest install, from the project root directory run `rake`, or `autotest`
|
28
31
|
(requires RSpec and optionally ZenTest for autotest support).
|
29
32
|
|
30
|
-
## Examples
|
33
|
+
## Examples (CouchRest Core)
|
31
34
|
|
32
35
|
Quick Start:
|
33
36
|
|
@@ -59,12 +62,50 @@ Creating and Querying Views:
|
|
59
62
|
})
|
60
63
|
puts @db.view('first/test')['rows'].inspect
|
61
64
|
|
62
|
-
## CouchRest::Model
|
63
65
|
|
64
|
-
|
66
|
+
## CouchRest::ExtendedDocument
|
67
|
+
|
68
|
+
CouchRest::ExtendedDocument is a DSL/ORM for CouchDB. Basically, ExtendedDocument seats on top of CouchRest Core to add the concept of Model.
|
69
|
+
ExtendedDocument offers a lot of the usual ORM tools such as optional yet defined schema, validation, callbacks, pagination, casting and much more.
|
70
|
+
|
71
|
+
### Model example
|
72
|
+
|
73
|
+
Check spec/couchrest/more and spec/fixtures/more for more examples
|
74
|
+
|
75
|
+
class Article < CouchRest::ExtendedDocument
|
76
|
+
use_database DB
|
77
|
+
unique_id :slug
|
78
|
+
|
79
|
+
view_by :date, :descending => true
|
80
|
+
view_by :user_id, :date
|
81
|
+
|
82
|
+
view_by :tags,
|
83
|
+
:map =>
|
84
|
+
"function(doc) {
|
85
|
+
if (doc['couchrest-type'] == 'Article' && doc.tags) {
|
86
|
+
doc.tags.forEach(function(tag){
|
87
|
+
emit(tag, 1);
|
88
|
+
});
|
89
|
+
}
|
90
|
+
}",
|
91
|
+
:reduce =>
|
92
|
+
"function(keys, values, rereduce) {
|
93
|
+
return sum(values);
|
94
|
+
}"
|
95
|
+
|
96
|
+
property :date
|
97
|
+
property :slug, :read_only => true
|
98
|
+
property :title
|
99
|
+
property :tags, :cast_as => ['String']
|
100
|
+
|
101
|
+
timestamps!
|
65
102
|
|
103
|
+
save_callback :before, :generate_slug_from_title
|
66
104
|
|
67
|
-
|
105
|
+
def generate_slug_from_title
|
106
|
+
self['slug'] = title.downcase.gsub(/[^a-z0-9]/,'-').squeeze('-').gsub(/^\-|\-$/,'') if new_document?
|
107
|
+
end
|
108
|
+
end
|
68
109
|
|
69
110
|
### Callbacks
|
70
111
|
|
@@ -114,4 +155,11 @@ Basically, you can paginate through the articles starting by the letter a, 5 art
|
|
114
155
|
Low level usage:
|
115
156
|
|
116
157
|
Article.paginate(:design_doc => 'Article', :view_name => 'by_date',
|
117
|
-
:per_page => 3, :page => 2, :descending => true, :key => Date.today, :include_docs => true)
|
158
|
+
:per_page => 3, :page => 2, :descending => true, :key => Date.today, :include_docs => true)
|
159
|
+
|
160
|
+
|
161
|
+
## Ruby on Rails
|
162
|
+
|
163
|
+
CouchRest is compatible with rails and can even be used a Rails plugin.
|
164
|
+
However, you might be interested in the CouchRest companion rails project:
|
165
|
+
[http://github.com/hpoydar/couchrest-rails](http://github.com/hpoydar/couchrest-rails)
|
data/history.txt
CHANGED
@@ -1,3 +1,47 @@
|
|
1
|
+
== 0.33
|
2
|
+
|
3
|
+
* Major enhancements
|
4
|
+
|
5
|
+
* Added a new Rack logger middleware letting you log/save requests/queries (Matt Aimonetti)
|
6
|
+
|
7
|
+
* Minor enhancements
|
8
|
+
|
9
|
+
* Added #amount_pages to a paginated result array (Matt Aimonetti)
|
10
|
+
* Ruby 1.9.2 compatible (Matt Aimonetti)
|
11
|
+
* Added a property? method for property cast as :boolean (John Wood)
|
12
|
+
* Added an option to force the deletion of a attachments (bypass 409s) (Matt Aimonetti)
|
13
|
+
* Created a new abstraction layer for the REST API (Matt Aimonetti)
|
14
|
+
* Bug fix: made ExtendedDocument#all compatible with Couch 0.10 (tc)
|
15
|
+
|
16
|
+
== 0.32
|
17
|
+
|
18
|
+
* Major enhancements
|
19
|
+
|
20
|
+
* ExtendedDocument.get doesn't raise an exception anymore. If no documents are found nil is returned.
|
21
|
+
* ExtendedDocument.get! works the say #get used to work and will raise an exception if a document isn't found.
|
22
|
+
|
23
|
+
* Minor enhancements
|
24
|
+
|
25
|
+
* Bug fix: Model.all(:keys => [1,2]) was not working (Matt Aimonetti)
|
26
|
+
* Added ValidationErrors#count in order to play nicely with Rails (Peter Wagenet)
|
27
|
+
* Bug fix: class proxy design doc refresh (Daniel Kirsh)
|
28
|
+
* Bug fix: the count method on the proxy collection was missing (Daniel Kirsch)
|
29
|
+
* Added #amount_pages to a paginated collection. (Matt Aimonetti)
|
30
|
+
|
31
|
+
== 0.31
|
32
|
+
|
33
|
+
* Major enhancements
|
34
|
+
|
35
|
+
* Created an abstraction HTTP layer to support different http adapters (Matt Aimonetti)
|
36
|
+
* Added ExtendedDocument.create({}) and #create!({}) so you don't have to do Model.new.create (Matt Aimonetti)
|
37
|
+
|
38
|
+
* Minor enhancements
|
39
|
+
|
40
|
+
* Added an init.rb file for easy usage as a Rails plugin (Aaron Quint)
|
41
|
+
* Bug fix: pagination shouldn't die on empty results (Arnaud Berthomier)
|
42
|
+
* Optimized ExtendedDocument.count to run about 3x faster (Matt Aimonetti)
|
43
|
+
* Added Float casting (Ryan Felton & Matt Aimonetti)
|
44
|
+
|
1
45
|
== 0.30
|
2
46
|
|
3
47
|
* Major enhancements
|
@@ -16,4 +60,4 @@
|
|
16
60
|
---
|
17
61
|
|
18
62
|
Unfortunately, before 0.30 we did not keep a track of the modifications made to CouchRest.
|
19
|
-
You can see the full commit history on GitHub: http://github.com/
|
63
|
+
You can see the full commit history on GitHub: http://github.com/couchrest/couchrest/commits/master/
|
data/lib/couchrest.rb
CHANGED
@@ -29,7 +29,7 @@ require 'couchrest/monkeypatches'
|
|
29
29
|
|
30
30
|
# = CouchDB, close to the metal
|
31
31
|
module CouchRest
|
32
|
-
VERSION = '0.
|
32
|
+
VERSION = '0.33' unless self.const_defined?("VERSION")
|
33
33
|
|
34
34
|
autoload :Server, 'couchrest/core/server'
|
35
35
|
autoload :Database, 'couchrest/core/database'
|
@@ -46,7 +46,13 @@ module CouchRest
|
|
46
46
|
autoload :ExtendedDocument, 'couchrest/more/extended_document'
|
47
47
|
autoload :CastedModel, 'couchrest/more/casted_model'
|
48
48
|
|
49
|
+
require File.join(File.dirname(__FILE__), 'couchrest', 'core', 'rest_api')
|
50
|
+
require File.join(File.dirname(__FILE__), 'couchrest', 'core', 'http_abstraction')
|
49
51
|
require File.join(File.dirname(__FILE__), 'couchrest', 'mixins')
|
52
|
+
|
53
|
+
# we extend CouchRest with the RestAPI module which gives us acess to
|
54
|
+
# the get, post, put, delete and copy
|
55
|
+
CouchRest.extend(::RestAPI)
|
50
56
|
|
51
57
|
# The CouchRest module methods handle the basic JSON serialization
|
52
58
|
# and deserialization, as well as query parameters. The module also includes
|
@@ -119,9 +125,9 @@ module CouchRest
|
|
119
125
|
}
|
120
126
|
end
|
121
127
|
|
122
|
-
# set proxy
|
128
|
+
# set proxy to use
|
123
129
|
def proxy url
|
124
|
-
|
130
|
+
HttpAbstraction.proxy = url
|
125
131
|
end
|
126
132
|
|
127
133
|
# ensure that a database exists
|
@@ -139,52 +145,6 @@ module CouchRest
|
|
139
145
|
cr.database(parsed[:database])
|
140
146
|
end
|
141
147
|
|
142
|
-
def put(uri, doc = nil)
|
143
|
-
payload = doc.to_json if doc
|
144
|
-
begin
|
145
|
-
JSON.parse(RestClient.put(uri, payload))
|
146
|
-
rescue Exception => e
|
147
|
-
if $DEBUG
|
148
|
-
raise "Error while sending a PUT request #{uri}\npayload: #{payload.inspect}\n#{e}"
|
149
|
-
else
|
150
|
-
raise e
|
151
|
-
end
|
152
|
-
end
|
153
|
-
end
|
154
|
-
|
155
|
-
def get(uri)
|
156
|
-
begin
|
157
|
-
JSON.parse(RestClient.get(uri), :max_nesting => false)
|
158
|
-
rescue => e
|
159
|
-
if $DEBUG
|
160
|
-
raise "Error while sending a GET request #{uri}\n: #{e}"
|
161
|
-
else
|
162
|
-
raise e
|
163
|
-
end
|
164
|
-
end
|
165
|
-
end
|
166
|
-
|
167
|
-
def post uri, doc = nil
|
168
|
-
payload = doc.to_json if doc
|
169
|
-
begin
|
170
|
-
JSON.parse(RestClient.post(uri, payload))
|
171
|
-
rescue Exception => e
|
172
|
-
if $DEBUG
|
173
|
-
raise "Error while sending a POST request #{uri}\npayload: #{payload.inspect}\n#{e}"
|
174
|
-
else
|
175
|
-
raise e
|
176
|
-
end
|
177
|
-
end
|
178
|
-
end
|
179
|
-
|
180
|
-
def delete uri
|
181
|
-
JSON.parse(RestClient.delete(uri))
|
182
|
-
end
|
183
|
-
|
184
|
-
def copy uri, destination
|
185
|
-
JSON.parse(RestClient.copy(uri, {'Destination' => destination}))
|
186
|
-
end
|
187
|
-
|
188
148
|
def paramify_url url, params = {}
|
189
149
|
if params && !params.empty?
|
190
150
|
query = params.collect do |k,v|
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module RestClientAdapter
|
2
|
+
|
3
|
+
module API
|
4
|
+
def proxy=(url)
|
5
|
+
RestClient.proxy = url
|
6
|
+
end
|
7
|
+
|
8
|
+
def proxy
|
9
|
+
RestClient.proxy
|
10
|
+
end
|
11
|
+
|
12
|
+
def get(uri, headers={})
|
13
|
+
RestClient.get(uri, headers)
|
14
|
+
end
|
15
|
+
|
16
|
+
def post(uri, payload, headers={})
|
17
|
+
RestClient.post(uri, payload, headers)
|
18
|
+
end
|
19
|
+
|
20
|
+
def put(uri, payload, headers={})
|
21
|
+
RestClient.put(uri, payload, headers)
|
22
|
+
end
|
23
|
+
|
24
|
+
def delete(uri, headers={})
|
25
|
+
RestClient.delete(uri, headers)
|
26
|
+
end
|
27
|
+
|
28
|
+
def copy(uri, headers)
|
29
|
+
RestClient::Request.execute( :method => :copy,
|
30
|
+
:url => uri,
|
31
|
+
:headers => headers)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
@@ -58,7 +58,7 @@ module CouchRest
|
|
58
58
|
keys = params.delete(:keys)
|
59
59
|
funcs = funcs.merge({:keys => keys}) if keys
|
60
60
|
url = CouchRest.paramify_url "#{@root}/_temp_view", params
|
61
|
-
JSON.parse(
|
61
|
+
JSON.parse(HttpAbstraction.post(url, funcs.to_json, {"Content-Type" => 'application/json'}))
|
62
62
|
end
|
63
63
|
|
64
64
|
# backwards compatibility is a plus
|
@@ -100,11 +100,8 @@ module CouchRest
|
|
100
100
|
|
101
101
|
# GET an attachment directly from CouchDB
|
102
102
|
def fetch_attachment(doc, name)
|
103
|
-
# slug = escape_docid(docid)
|
104
|
-
# name = CGI.escape(name)
|
105
103
|
uri = url_for_attachment(doc, name)
|
106
|
-
|
107
|
-
# "#{@uri}/#{slug}/#{name}"
|
104
|
+
HttpAbstraction.get uri
|
108
105
|
end
|
109
106
|
|
110
107
|
# PUT an attachment directly to CouchDB
|
@@ -112,14 +109,25 @@ module CouchRest
|
|
112
109
|
docid = escape_docid(doc['_id'])
|
113
110
|
name = CGI.escape(name)
|
114
111
|
uri = url_for_attachment(doc, name)
|
115
|
-
JSON.parse(
|
112
|
+
JSON.parse(HttpAbstraction.put(uri, file, options))
|
116
113
|
end
|
117
114
|
|
118
115
|
# DELETE an attachment directly from CouchDB
|
119
|
-
def delete_attachment
|
116
|
+
def delete_attachment(doc, name, force=false)
|
120
117
|
uri = url_for_attachment(doc, name)
|
121
118
|
# this needs a rev
|
122
|
-
|
119
|
+
begin
|
120
|
+
JSON.parse(HttpAbstraction.delete(uri))
|
121
|
+
rescue Exception => error
|
122
|
+
if force
|
123
|
+
# get over a 409
|
124
|
+
doc = get(doc['_id'])
|
125
|
+
uri = url_for_attachment(doc, name)
|
126
|
+
JSON.parse(HttpAbstraction.delete(uri))
|
127
|
+
else
|
128
|
+
error
|
129
|
+
end
|
130
|
+
end
|
123
131
|
end
|
124
132
|
|
125
133
|
# Save a document to CouchDB. This will use the <tt>_id</tt> field from
|
@@ -146,7 +154,7 @@ module CouchRest
|
|
146
154
|
slug = escape_docid(doc['_id'])
|
147
155
|
begin
|
148
156
|
CouchRest.put "#{@root}/#{slug}", doc
|
149
|
-
rescue
|
157
|
+
rescue HttpAbstraction::ResourceNotFound
|
150
158
|
p "resource not found when saving even tho an id was passed"
|
151
159
|
slug = doc['_id'] = @server.next_uuid
|
152
160
|
CouchRest.put "#{@root}/#{slug}", doc
|
@@ -252,7 +260,7 @@ module CouchRest
|
|
252
260
|
def recreate!
|
253
261
|
delete!
|
254
262
|
create!
|
255
|
-
rescue
|
263
|
+
rescue HttpAbstraction::ResourceNotFound
|
256
264
|
ensure
|
257
265
|
create!
|
258
266
|
end
|
@@ -3,10 +3,6 @@ require 'delegate'
|
|
3
3
|
module CouchRest
|
4
4
|
class Document < Response
|
5
5
|
include CouchRest::Mixins::Attachments
|
6
|
-
|
7
|
-
# def self.inherited(subklass)
|
8
|
-
# subklass.send(:extlib_inheritable_accessor, :database)
|
9
|
-
# end
|
10
6
|
|
11
7
|
extlib_inheritable_accessor :database
|
12
8
|
attr_accessor :database
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'couchrest/core/adapters/restclient'
|
2
|
+
|
3
|
+
# Abstraction layet for HTTP communications.
|
4
|
+
#
|
5
|
+
# By defining a basic API that CouchRest is relying on,
|
6
|
+
# it allows for easy experimentations and implementations of various libraries.
|
7
|
+
#
|
8
|
+
# Most of the API is based on the RestClient API that was used in the early version of CouchRest.
|
9
|
+
#
|
10
|
+
module HttpAbstraction
|
11
|
+
|
12
|
+
# here is the list of exception expected by CouchRest
|
13
|
+
# please convert the underlying errors in this set of known
|
14
|
+
# exceptions.
|
15
|
+
class ResourceNotFound < StandardError; end
|
16
|
+
class RequestFailed < StandardError; end
|
17
|
+
class RequestTimeout < StandardError; end
|
18
|
+
class ServerBrokeConnection < StandardError; end
|
19
|
+
class Conflict < StandardError; end
|
20
|
+
|
21
|
+
|
22
|
+
# # Here is the API you need to implement if you want to write a new adapter
|
23
|
+
# # See adapters/restclient.rb for more information.
|
24
|
+
#
|
25
|
+
# def self.proxy=(url)
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# def self.proxy
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# def self.get(uri, headers=nil)
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# def self.post(uri, payload, headers=nil)
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# def self.put(uri, payload, headers=nil)
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# def self.delete(uri, headers=nil)
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# def self.copy(uri, headers)
|
44
|
+
# end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
HttpAbstraction.extend(RestClientAdapter::API)
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module RestAPI
|
2
|
+
|
3
|
+
def put(uri, doc = nil)
|
4
|
+
payload = doc.to_json if doc
|
5
|
+
begin
|
6
|
+
JSON.parse(HttpAbstraction.put(uri, payload))
|
7
|
+
rescue Exception => e
|
8
|
+
if $DEBUG
|
9
|
+
raise "Error while sending a PUT request #{uri}\npayload: #{payload.inspect}\n#{e}"
|
10
|
+
else
|
11
|
+
raise e
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def get(uri)
|
17
|
+
begin
|
18
|
+
JSON.parse(HttpAbstraction.get(uri), :max_nesting => false)
|
19
|
+
rescue => e
|
20
|
+
if $DEBUG
|
21
|
+
raise "Error while sending a GET request #{uri}\n: #{e}"
|
22
|
+
else
|
23
|
+
raise e
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def post(uri, doc = nil)
|
29
|
+
payload = doc.to_json if doc
|
30
|
+
begin
|
31
|
+
JSON.parse(HttpAbstraction.post(uri, payload))
|
32
|
+
rescue Exception => e
|
33
|
+
if $DEBUG
|
34
|
+
raise "Error while sending a POST request #{uri}\npayload: #{payload.inspect}\n#{e}"
|
35
|
+
else
|
36
|
+
raise e
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def delete(uri)
|
42
|
+
JSON.parse(HttpAbstraction.delete(uri))
|
43
|
+
end
|
44
|
+
|
45
|
+
def copy(uri, destination)
|
46
|
+
JSON.parse(HttpAbstraction.copy(uri, {'Destination' => destination}))
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|