couchrest 0.33 → 0.34

Sign up to get free protection for your applications and to get access to all the features.
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