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.
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