couchrest 0.33 → 0.34

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 (45) hide show
  1. data/README.md +8 -127
  2. data/Rakefile +20 -36
  3. data/THANKS.md +2 -1
  4. data/history.txt +25 -0
  5. data/lib/couchrest.rb +5 -4
  6. data/lib/couchrest/core/database.rb +26 -21
  7. data/lib/couchrest/core/document.rb +4 -3
  8. data/lib/couchrest/helper/streamer.rb +11 -4
  9. data/lib/couchrest/mixins/attribute_protection.rb +74 -0
  10. data/lib/couchrest/mixins/callbacks.rb +187 -138
  11. data/lib/couchrest/mixins/collection.rb +3 -16
  12. data/lib/couchrest/mixins/extended_attachments.rb +1 -1
  13. data/lib/couchrest/mixins/extended_document_mixins.rb +1 -0
  14. data/lib/couchrest/mixins/properties.rb +71 -44
  15. data/lib/couchrest/mixins/validation.rb +18 -29
  16. data/lib/couchrest/more/casted_model.rb +29 -1
  17. data/lib/couchrest/more/extended_document.rb +73 -25
  18. data/lib/couchrest/more/property.rb +20 -1
  19. data/lib/couchrest/support/class.rb +81 -67
  20. data/lib/couchrest/support/rails.rb +12 -5
  21. data/lib/couchrest/validation/auto_validate.rb +5 -9
  22. data/lib/couchrest/validation/validators/confirmation_validator.rb +11 -3
  23. data/lib/couchrest/validation/validators/format_validator.rb +8 -3
  24. data/lib/couchrest/validation/validators/length_validator.rb +10 -5
  25. data/lib/couchrest/validation/validators/numeric_validator.rb +6 -1
  26. data/lib/couchrest/validation/validators/required_field_validator.rb +8 -3
  27. data/spec/couchrest/core/couchrest_spec.rb +48 -2
  28. data/spec/couchrest/core/database_spec.rb +22 -10
  29. data/spec/couchrest/core/document_spec.rb +9 -1
  30. data/spec/couchrest/helpers/streamer_spec.rb +31 -2
  31. data/spec/couchrest/more/attribute_protection_spec.rb +94 -0
  32. data/spec/couchrest/more/casted_extended_doc_spec.rb +2 -4
  33. data/spec/couchrest/more/casted_model_spec.rb +230 -1
  34. data/spec/couchrest/more/extended_doc_attachment_spec.rb +2 -2
  35. data/spec/couchrest/more/extended_doc_spec.rb +173 -15
  36. data/spec/couchrest/more/extended_doc_view_spec.rb +17 -10
  37. data/spec/couchrest/more/property_spec.rb +97 -3
  38. data/spec/fixtures/more/article.rb +4 -3
  39. data/spec/fixtures/more/card.rb +1 -1
  40. data/spec/fixtures/more/cat.rb +5 -3
  41. data/spec/fixtures/more/event.rb +4 -1
  42. data/spec/fixtures/more/invoice.rb +2 -2
  43. data/spec/fixtures/more/person.rb +1 -0
  44. data/spec/fixtures/more/user.rb +22 -0
  45. metadata +46 -13
data/README.md CHANGED
@@ -14,11 +14,6 @@ Note: CouchRest only support CouchDB 0.9.0 or newer.
14
14
 
15
15
  $ sudo gem install couchrest
16
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
21
-
22
17
  ### Relax, it's RESTful
23
18
 
24
19
  CouchRest rests on top of a HTTP abstraction layer using by default Heroku’s excellent REST Client Ruby HTTP wrapper.
@@ -30,136 +25,22 @@ The most complete documentation is the spec/ directory. To validate your
30
25
  CouchRest install, from the project root directory run `rake`, or `autotest`
31
26
  (requires RSpec and optionally ZenTest for autotest support).
32
27
 
33
- ## Examples (CouchRest Core)
34
-
35
- Quick Start:
36
-
37
- # with !, it creates the database if it doesn't already exist
38
- @db = CouchRest.database!("http://127.0.0.1:5984/couchrest-test")
39
- response = @db.save_doc({:key => 'value', 'another key' => 'another value'})
40
- doc = @db.get(response['id'])
41
- puts doc.inspect
42
-
43
- Bulk Save:
44
-
45
- @db.bulk_save([
46
- {"wild" => "and random"},
47
- {"mild" => "yet local"},
48
- {"another" => ["set","of","keys"]}
49
- ])
50
- # returns ids and revs of the current docs
51
- puts @db.documents.inspect
52
-
53
- Creating and Querying Views:
54
-
55
- @db.save_doc({
56
- "_id" => "_design/first",
57
- :views => {
58
- :test => {
59
- :map => "function(doc){for(var w in doc){ if(!w.match(/^_/))emit(w,doc[w])}}"
60
- }
61
- }
62
- })
63
- puts @db.view('first/test')['rows'].inspect
64
-
65
-
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
- }"
28
+ ## Docs
95
29
 
96
- property :date
97
- property :slug, :read_only => true
98
- property :title
99
- property :tags, :cast_as => ['String']
30
+ API: [http://rdoc.info/projects/couchrest/couchrest](http://rdoc.info/projects/couchrest/couchrest)
100
31
 
101
- timestamps!
32
+ Check the wiki for documentation and examples [http://wiki.github.com/couchrest/couchrest](http://wiki.github.com/couchrest/couchrest)
102
33
 
103
- save_callback :before, :generate_slug_from_title
34
+ ## Contact
104
35
 
105
- def generate_slug_from_title
106
- self['slug'] = title.downcase.gsub(/[^a-z0-9]/,'-').squeeze('-').gsub(/^\-|\-$/,'') if new_document?
107
- end
108
- end
36
+ Please post bugs, suggestions and patches to the bug tracker at <http://jchris.lighthouseapp.com/projects/17807-couchrest/overview>.
109
37
 
110
- ### Callbacks
38
+ Follow us on Twitter: http://twitter.com/couchrest
111
39
 
112
- `CouchRest::ExtendedDocuments` instances have 2 callbacks already defined for you:
113
- `create_callback`, `save_callback`, `update_callback` and `destroy_callback`
114
-
115
- In your document inherits from `CouchRest::ExtendedDocument`, define your callback as follows:
116
-
117
- save_callback :before, :generate_slug_from_name
118
-
119
- CouchRest uses a mixin you can find in lib/mixins/callbacks which is extracted from Rails 3, here are some simple usage examples:
120
-
121
- save_callback :before, :before_method
122
- save_callback :after, :after_method, :if => :condition
123
- save_callback :around {|r| stuff; yield; stuff }
124
-
125
- Check the mixin or the ExtendedDocument class to see how to implement your own callbacks.
126
-
127
- ### Casting
128
-
129
- Often, you will want to store multiple objects within a document, to be able to retrieve your objects when you load the document,
130
- you can define some casting rules.
131
-
132
- property :casted_attribute, :cast_as => 'WithCastedModelMixin'
133
- property :keywords, :cast_as => ["String"]
134
-
135
- If you want to cast an array of instances from a specific Class, use the trick shown above ["ClassName"]
136
-
137
- ### Pagination
138
-
139
- Pagination is available in any ExtendedDocument classes. Here are some usage examples:
140
-
141
- basic usage:
142
-
143
- Article.all.paginate(:page => 1, :per_page => 5)
144
-
145
- note: the above query will look like: `GET /db/_design/Article/_view/all?include_docs=true&skip=0&limit=5&reduce=false` and only fetch 5 documents.
146
-
147
- Slightly more advance usage:
148
-
149
- Article.by_name(:startkey => 'a', :endkey => {}).paginate(:page => 1, :per_page => 5)
150
-
151
- note: the above query will look like: `GET /db/_design/Article/_view/by_name?startkey=%22a%22&limit=5&skip=0&endkey=%7B%7D&include_docs=true`
152
- Basically, you can paginate through the articles starting by the letter a, 5 articles at a time.
153
-
154
-
155
- Low level usage:
156
-
157
- Article.paginate(:design_doc => 'Article', :view_name => 'by_date',
158
- :per_page => 3, :page => 2, :descending => true, :key => Date.today, :include_docs => true)
159
-
40
+ Also, check http://twitter.com/#search?q=%23couchrest
160
41
 
161
42
  ## Ruby on Rails
162
43
 
163
44
  CouchRest is compatible with rails and can even be used a Rails plugin.
164
45
  However, you might be interested in the CouchRest companion rails project:
165
- [http://github.com/hpoydar/couchrest-rails](http://github.com/hpoydar/couchrest-rails)
46
+ [http://github.com/hpoydar/couchrest-rails](http://github.com/hpoydar/couchrest-rails)
data/Rakefile CHANGED
@@ -1,9 +1,7 @@
1
1
  require 'rake'
2
2
  require "rake/rdoctask"
3
- require 'rake/gempackagetask'
4
3
  require File.join(File.expand_path(File.dirname(__FILE__)),'lib','couchrest')
5
4
 
6
-
7
5
  begin
8
6
  require 'spec/rake/spectask'
9
7
  rescue LoadError
@@ -14,45 +12,31 @@ EOS
14
12
  exit(0)
15
13
  end
16
14
 
17
- spec = Gem::Specification.new do |s|
18
- s.name = "couchrest"
19
- s.version = CouchRest::VERSION
20
- s.date = "2008-11-22"
21
- s.summary = "Lean and RESTful interface to CouchDB."
22
- s.email = "jchris@apache.org"
23
- s.homepage = "http://github.com/jchris/couchrest"
24
- s.description = "CouchRest provides a simple interface on top of CouchDB's RESTful HTTP API, as well as including some utility scripts for managing views and attachments."
25
- s.has_rdoc = true
26
- s.authors = ["J. Chris Anderson", "Matt Aimonetti"]
27
- s.files = %w( LICENSE README.md Rakefile THANKS.md history.txt) +
28
- Dir["{examples,lib,spec,utils}/**/*"] -
29
- Dir["spec/tmp"]
30
- s.extra_rdoc_files = %w( README.md LICENSE THANKS.md )
31
- s.require_path = "lib"
32
- s.add_dependency("rest-client", ">= 0.5")
33
- s.add_dependency("mime-types", ">= 1.15")
34
- end
35
-
36
-
37
- desc "Create .gemspec file (useful for github)"
38
- task :gemspec do
39
- filename = "#{spec.name}.gemspec"
40
- File.open(filename, "w") do |f|
41
- f.puts spec.to_ruby
15
+ begin
16
+ require 'jeweler'
17
+ Jeweler::Tasks.new do |gemspec|
18
+ gemspec.name = "couchrest"
19
+ gemspec.summary = "Lean and RESTful interface to CouchDB."
20
+ gemspec.description = "CouchRest provides a simple interface on top of CouchDB's RESTful HTTP API, as well as including some utility scripts for managing views and attachments."
21
+ gemspec.email = "jchris@apache.org"
22
+ gemspec.homepage = "http://github.com/couchrest/couchrest"
23
+ gemspec.authors = ["J. Chris Anderson", "Matt Aimonetti", "Marcos Tapajos"]
24
+ gemspec.extra_rdoc_files = %w( README.md LICENSE THANKS.md )
25
+ gemspec.files = %w( LICENSE README.md Rakefile THANKS.md history.txt) + Dir["{examples,lib,spec,utils}/**/*"] - Dir["spec/tmp"]
26
+ gemspec.has_rdoc = true
27
+ gemspec.add_dependency("rest-client", ">= 0.5")
28
+ gemspec.add_dependency("mime-types", ">= 1.15")
29
+ gemspec.version = CouchRest::VERSION
30
+ gemspec.date = "2008-11-22"
31
+ gemspec.require_path = "lib"
42
32
  end
43
- end
44
-
45
- Rake::GemPackageTask.new(spec) do |pkg|
46
- pkg.gem_spec = spec
47
- end
48
-
49
- desc "Install the gem locally"
50
- task :install => [:package] do
51
- sh %{sudo gem install pkg/couchrest-#{CouchRest::VERSION}}
33
+ rescue LoadError
34
+ puts "Jeweler not available. Install it with: gem install jeweler"
52
35
  end
53
36
 
54
37
  desc "Run all specs"
55
38
  Spec::Rake::SpecTask.new('spec') do |t|
39
+ t.spec_opts = ["--color"]
56
40
  t.spec_files = FileList['spec/**/*_spec.rb']
57
41
  end
58
42
 
data/THANKS.md CHANGED
@@ -12,7 +12,8 @@ changes. A list of these people is included below.
12
12
  * [Jonathan S. Katz](http://github.com/jkatz)
13
13
  * [Matt Lyon](http://mattly.tumblr.com/)
14
14
  * Simon Rozet (simon /at/ rozet /dot/ name)
15
+ * [Marcos Tapajós](http://tapajos.me)
15
16
 
16
- Patches are welcome. The primary source for this software project is [on Github](http://github.com/jchris/couchrest/tree/master)
17
+ Patches are welcome. The primary source for this software project is [on Github](http://github.com/couchrest/couchrest)
17
18
 
18
19
  A lot of people have active forks - thank you all - even the patches I don't end up using are helpful.
data/history.txt CHANGED
@@ -1,3 +1,28 @@
1
+ == 0.34
2
+
3
+ * Major enhancements
4
+
5
+ * Added support for https database URIs. (Mathias Meyer)
6
+ * Changing some validations to be compatible with activemodel. (Marcos Tapajós)
7
+ * Adds attribute protection to properties. (Will Leinweber)
8
+ * Improved CouchRest::Database#save_doc, added "batch" mode to significantly speed up saves at cost of lower durability gurantees. (Igal Koshevoy)
9
+ * Added CouchRest::Database#bulk_save_doc and #batch_save_doc as human-friendlier wrappers around #save_doc. (Igal Koshevoy)
10
+
11
+ * Minor enhancements
12
+
13
+ * Fix content_type handling for attachments
14
+ * Fixed a bug in the pagination code that caused it to paginate over records outside of the scope of the view parameters.(John Wood)
15
+ * Removed amount_pages calculation for the pagination collection, since it cannot be reliably calculated without a view.(John Wood)
16
+ * Bug fix: http://github.com/couchrest/couchrest/issues/#issue/2 (Luke Burton)
17
+ * Bug fix: http://github.com/couchrest/couchrest/issues/#issue/1 (Marcos Tapajós)
18
+ * Removed the Database class deprecation notices (Matt Aimonetti)
19
+ * Adding support to :cast_as => 'Date'. (Marcos Tapajós)
20
+ * Improve documentation (Marcos Tapajós)
21
+ * Streamer fixes (Julien Sanchez)
22
+ * Fix Save on Document & ExtendedDocument crashed if bulk (Julien Sanchez)
23
+ * Fix Initialization of ExtendentDocument model shouldn't failed on a nil value in argument (deepj)
24
+ * Change to use Jeweler and Gemcutter (Marcos Tapajós)
25
+
1
26
  == 0.33
2
27
 
3
28
  * Major enhancements
data/lib/couchrest.rb CHANGED
@@ -28,7 +28,7 @@ require 'couchrest/monkeypatches'
28
28
 
29
29
  # = CouchDB, close to the metal
30
30
  module CouchRest
31
- VERSION = '0.33' unless self.const_defined?("VERSION")
31
+ VERSION = '0.34' unless self.const_defined?("VERSION")
32
32
 
33
33
  autoload :Server, 'couchrest/core/server'
34
34
  autoload :Database, 'couchrest/core/database'
@@ -48,6 +48,7 @@ module CouchRest
48
48
  require File.join(File.dirname(__FILE__), 'couchrest', 'core', 'rest_api')
49
49
  require File.join(File.dirname(__FILE__), 'couchrest', 'core', 'http_abstraction')
50
50
  require File.join(File.dirname(__FILE__), 'couchrest', 'mixins')
51
+ require File.join(File.dirname(__FILE__), 'couchrest', 'support', 'rails') if defined?(Rails)
51
52
 
52
53
  # we extend CouchRest with the RestAPI module which gives us acess to
53
54
  # the get, post, put, delete and copy
@@ -95,14 +96,14 @@ module CouchRest
95
96
 
96
97
  def parse url
97
98
  case url
98
- when /^http:\/\/(.*)\/(.*)\/(.*)/
99
+ when /^https?:\/\/(.*)\/(.*)\/(.*)/
99
100
  host = $1
100
101
  db = $2
101
102
  docid = $3
102
- when /^http:\/\/(.*)\/(.*)/
103
+ when /^https?:\/\/(.*)\/(.*)/
103
104
  host = $1
104
105
  db = $2
105
- when /^http:\/\/(.*)/
106
+ when /^https?:\/\/(.*)/
106
107
  host = $1
107
108
  when /(.*)\/(.*)\/(.*)/
108
109
  host = $1
@@ -129,7 +129,7 @@ module CouchRest
129
129
  end
130
130
  end
131
131
  end
132
-
132
+
133
133
  # Save a document to CouchDB. This will use the <tt>_id</tt> field from
134
134
  # the document as the id for PUT, or request a new UUID from CouchDB, if
135
135
  # no <tt>_id</tt> is present on the document. IDs are attached to
@@ -139,13 +139,25 @@ module CouchRest
139
139
  #
140
140
  # If <tt>bulk</tt> is true (false by default) the document is cached for bulk-saving later.
141
141
  # Bulk saving happens automatically when #bulk_save_cache limit is exceded, or on the next non bulk save.
142
- def save_doc(doc, bulk = false)
142
+ #
143
+ # If <tt>batch</tt> is true (false by default) the document is saved in
144
+ # batch mode, "used to achieve higher throughput at the cost of lower
145
+ # guarantees. When [...] sent using this option, it is not immediately
146
+ # written to disk. Instead it is stored in memory on a per-user basis for a
147
+ # second or so (or the number of docs in memory reaches a certain point).
148
+ # After the threshold has passed, the docs are committed to disk. Instead
149
+ # of waiting for the doc to be written to disk before responding, CouchDB
150
+ # sends an HTTP 202 Accepted response immediately. batch=ok is not suitable
151
+ # for crucial data, but it ideal for applications like logging which can
152
+ # accept the risk that a small proportion of updates could be lost due to a
153
+ # crash."
154
+ def save_doc(doc, bulk = false, batch = false)
143
155
  if doc['_attachments']
144
156
  doc['_attachments'] = encode_attachments(doc['_attachments'])
145
157
  end
146
158
  if bulk
147
159
  @bulk_save_cache << doc
148
- return bulk_save if @bulk_save_cache.length >= @bulk_save_cache_limit
160
+ bulk_save if @bulk_save_cache.length >= @bulk_save_cache_limit
149
161
  return {"ok" => true} # Compatibility with Document#save
150
162
  elsif !bulk && @bulk_save_cache.length > 0
151
163
  bulk_save
@@ -153,7 +165,9 @@ module CouchRest
153
165
  result = if doc['_id']
154
166
  slug = escape_docid(doc['_id'])
155
167
  begin
156
- CouchRest.put "#{@root}/#{slug}", doc
168
+ uri = "#{@root}/#{slug}"
169
+ uri << "?batch=ok" if batch
170
+ CouchRest.put uri, doc
157
171
  rescue HttpAbstraction::ResourceNotFound
158
172
  p "resource not found when saving even tho an id was passed"
159
173
  slug = doc['_id'] = @server.next_uuid
@@ -175,12 +189,15 @@ module CouchRest
175
189
  result
176
190
  end
177
191
 
178
- ### DEPRECATION NOTICE
179
- def save(doc, bulk=false)
180
- puts "CouchRest::Database's save method is being deprecated, please use save_doc instead"
181
- save_doc(doc, bulk)
192
+ # Save a document to CouchDB in bulk mode. See #save_doc's +bulk+ argument.
193
+ def bulk_save_doc(doc)
194
+ save_doc(doc, true)
195
+ end
196
+
197
+ # Save a document to CouchDB in batch mode. See #save_doc's +batch+ argument.
198
+ def batch_save_doc(doc)
199
+ save_doc(doc, false, true)
182
200
  end
183
-
184
201
 
185
202
  # POST an array of documents to CouchDB. If any of the documents are
186
203
  # missing ids, supply one from the uuid cache.
@@ -219,12 +236,6 @@ module CouchRest
219
236
  CouchRest.delete "#{@root}/#{slug}?rev=#{doc['_rev']}"
220
237
  end
221
238
 
222
- ### DEPRECATION NOTICE
223
- def delete(doc, bulk=false)
224
- puts "CouchRest::Database's delete method is being deprecated, please use delete_doc instead"
225
- delete_doc(doc, bulk)
226
- end
227
-
228
239
  # COPY an existing document to a new id. If the destination id currently exists, a rev must be provided.
229
240
  # <tt>dest</tt> can take one of two forms if overwriting: "id_to_overwrite?rev=revision" or the actual doc
230
241
  # hash with a '_rev' key
@@ -239,12 +250,6 @@ module CouchRest
239
250
  CouchRest.copy "#{@root}/#{slug}", destination
240
251
  end
241
252
 
242
- ### DEPRECATION NOTICE
243
- def copy(doc, dest)
244
- puts "CouchRest::Database's copy method is being deprecated, please use copy_doc instead"
245
- copy_doc(doc, dest)
246
- end
247
-
248
253
  # Compact the database, removing old document revisions and optimizing space use.
249
254
  def compact!
250
255
  CouchRest.post "#{@root}/_compact"
@@ -23,9 +23,10 @@ module CouchRest
23
23
  end
24
24
 
25
25
  # returns true if the document has never been saved
26
- def new_document?
26
+ def new?
27
27
  !rev
28
28
  end
29
+ alias :new_document? :new?
29
30
 
30
31
  # Saves the document to the db using create or update. Also runs the :save
31
32
  # callbacks. Sets the <tt>_id</tt> and <tt>_rev</tt> fields based on
@@ -63,8 +64,8 @@ module CouchRest
63
64
 
64
65
  # Returns the CouchDB uri for the document
65
66
  def uri(append_rev = false)
66
- return nil if new_document?
67
- couch_uri = "http://#{database.root}/#{CGI.escape(id)}"
67
+ return nil if new?
68
+ couch_uri = "#{database.root}/#{CGI.escape(id)}"
68
69
  if append_rev == true
69
70
  couch_uri << "?rev=#{rev}"
70
71
  elsif append_rev.kind_of?(Integer)
@@ -7,15 +7,22 @@ module CouchRest
7
7
 
8
8
  # Stream a view, yielding one row at a time. Shells out to <tt>curl</tt> to keep RAM usage low when you have millions of rows.
9
9
  def view name, params = nil, &block
10
- urlst = /^_/.match(name) ? "#{@db.root}/#{name}" : "#{@db.root}/_view/#{name}"
10
+ urlst = if /^_/.match(name) then
11
+ "#{@db.root}/#{name}"
12
+ else
13
+ name = name.split('/')
14
+ dname = name.shift
15
+ vname = name.join('/')
16
+ "#{@db.root}/_design/#{dname}/_view/#{vname}"
17
+ end
11
18
  url = CouchRest.paramify_url urlst, params
12
19
  # puts "stream #{url}"
13
20
  first = nil
14
- IO.popen("curl --silent #{url}") do |view|
21
+ IO.popen("curl --silent \"#{url}\"") do |view|
15
22
  first = view.gets # discard header
16
23
  while line = view.gets
17
24
  row = parse_line(line)
18
- block.call row
25
+ block.call row unless row.nil? # last line "}]" discarded
19
26
  end
20
27
  end
21
28
  parse_first(first)
@@ -41,4 +48,4 @@ module CouchRest
41
48
  end
42
49
 
43
50
  end
44
- end
51
+ end