norr-couchrest 0.30.4 → 0.33.01

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. data/README.md +58 -10
  2. data/history.txt +45 -1
  3. data/lib/couchrest.rb +9 -49
  4. data/lib/couchrest/core/adapters/restclient.rb +35 -0
  5. data/lib/couchrest/core/database.rb +18 -10
  6. data/lib/couchrest/core/document.rb +0 -4
  7. data/lib/couchrest/core/http_abstraction.rb +48 -0
  8. data/lib/couchrest/core/rest_api.rb +49 -0
  9. data/lib/couchrest/middlewares/logger.rb +263 -0
  10. data/lib/couchrest/mixins/attachments.rb +2 -2
  11. data/lib/couchrest/mixins/class_proxy.rb +5 -1
  12. data/lib/couchrest/mixins/collection.rb +23 -6
  13. data/lib/couchrest/mixins/design_doc.rb +5 -0
  14. data/lib/couchrest/mixins/document_queries.rb +33 -4
  15. data/lib/couchrest/mixins/properties.rb +30 -4
  16. data/lib/couchrest/mixins/views.rb +5 -13
  17. data/lib/couchrest/monkeypatches.rb +59 -59
  18. data/lib/couchrest/more/casted_model.rb +3 -2
  19. data/lib/couchrest/more/extended_document.rb +18 -1
  20. data/lib/couchrest/validation/validation_errors.rb +7 -0
  21. data/spec/couchrest/core/couchrest_spec.rb +2 -2
  22. data/spec/couchrest/core/database_spec.rb +19 -5
  23. data/spec/couchrest/core/design_spec.rb +1 -1
  24. data/spec/couchrest/core/document_spec.rb +1 -1
  25. data/spec/couchrest/core/server_spec.rb +1 -1
  26. data/spec/couchrest/helpers/pager_spec.rb +1 -1
  27. data/spec/couchrest/helpers/streamer_spec.rb +1 -1
  28. data/spec/couchrest/more/casted_extended_doc_spec.rb +1 -1
  29. data/spec/couchrest/more/casted_model_spec.rb +1 -1
  30. data/spec/couchrest/more/extended_doc_attachment_spec.rb +1 -1
  31. data/spec/couchrest/more/extended_doc_spec.rb +27 -2
  32. data/spec/couchrest/more/extended_doc_subclass_spec.rb +1 -1
  33. data/spec/couchrest/more/extended_doc_view_spec.rb +21 -9
  34. data/spec/couchrest/more/property_spec.rb +50 -1
  35. data/spec/spec_helper.rb +1 -1
  36. 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
- Easy Install is moving to RubyForge, heads up for the gem.
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
- The core of Couchrest is Heroku’s excellent REST Client Ruby HTTP wrapper.
20
- REST Client takes all the nastyness of Net::HTTP and gives is a pretty face,
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
- CouchRest::Model has been deprecated and replaced by CouchRest::ExtendedDocument
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
- ## CouchRest::ExtendedDocument
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)
@@ -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/mattetti/couchrest/commits/master/
63
+ You can see the full commit history on GitHub: http://github.com/couchrest/couchrest/commits/master/
@@ -29,7 +29,7 @@ require 'couchrest/monkeypatches'
29
29
 
30
30
  # = CouchDB, close to the metal
31
31
  module CouchRest
32
- VERSION = '0.30' unless self.const_defined?("VERSION")
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 for RestClient to use
128
+ # set proxy to use
123
129
  def proxy url
124
- RestClient.proxy = url
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(RestClient.post(url, funcs.to_json, {"Content-Type" => 'application/json'}))
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
- RestClient.get uri
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(RestClient.put(uri, file, options))
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 doc, name
116
+ def delete_attachment(doc, name, force=false)
120
117
  uri = url_for_attachment(doc, name)
121
118
  # this needs a rev
122
- JSON.parse(RestClient.delete(uri))
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 RestClient::ResourceNotFound
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 RestClient::ResourceNotFound
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