couchrest 1.0.2 → 1.1.0.pre

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -1,4 +1,5 @@
1
1
  .DS_Store
2
2
  html/*
3
3
  pkg
4
- *.swp
4
+ *.swp
5
+ Gemfile.lock
data/README.md CHANGED
@@ -10,6 +10,12 @@ CouchRest is designed to make a simple base for application and framework-specif
10
10
 
11
11
  **Note: CouchRest only support CouchDB 0.9.0 or newer. Some features requires CouchDB 0.10.0 or newer.**
12
12
 
13
+ ## Important Upgrade Notice
14
+
15
+ ### 2011-04-04: Time#to_json no longer overwritten!
16
+
17
+ Now sticking to JSON standard format. Ensure you views using Time will be ordered correctly after upgrade!
18
+
13
19
  ## Easy Install
14
20
 
15
21
  $ sudo gem install couchrest
data/Rakefile CHANGED
@@ -20,14 +20,14 @@ end
20
20
 
21
21
  desc "Run all specs"
22
22
  Spec::Rake::SpecTask.new('spec') do |t|
23
- t.spec_opts = ["--color"]
24
- t.spec_files = FileList['spec/**/*_spec.rb']
23
+ t.spec_opts = ["--color"]
24
+ t.spec_files = FileList['spec/**/*_spec.rb']
25
25
  end
26
26
 
27
27
  desc "Print specdocs"
28
28
  Spec::Rake::SpecTask.new(:doc) do |t|
29
- t.spec_opts = ["--format", "specdoc"]
30
- t.spec_files = FileList['spec/*_spec.rb']
29
+ t.spec_opts = ["--format", "specdoc"]
30
+ t.spec_files = FileList['spec/*_spec.rb']
31
31
  end
32
32
 
33
33
  desc "Generate the rdoc"
@@ -40,12 +40,3 @@ end
40
40
 
41
41
  desc "Run the rspec"
42
42
  task :default => :spec
43
-
44
- module Rake
45
- def self.remove_task(task_name)
46
- Rake.application.instance_variable_get('@tasks').delete(task_name.to_s)
47
- end
48
- end
49
-
50
- Rake.remove_task("github:release")
51
- Rake.remove_task("release")
data/couchrest.gemspec CHANGED
@@ -2,11 +2,11 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{couchrest}
5
- s.version = "1.0.2"
5
+ s.version = "1.1.0.pre"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["J. Chris Anderson", "Matt Aimonetti", "Marcos Tapajos", "Will Leinweber", "Sam Lown"]
9
- s.date = %q{2011-03-13}
9
+ s.date = %q{2011-04-06}
10
10
  s.description = %q{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.}
11
11
  s.email = %q{jchris@apache.org}
12
12
 
data/history.txt CHANGED
@@ -1,3 +1,21 @@
1
+ == 1.1.0.rc
2
+
3
+ * Major changes
4
+ * Time#to_json monkey patch removed! Standard JSON methods now used instead.
5
+
6
+ * Minor alterations
7
+ * Named doc replication (thanks @ahamid)
8
+ * Database#update_doc no longer requires document to be returned from block (thanks @ferrous26)
9
+ * Streamer now available for all queries that return multiple documents (thanks @pcapr for pointer)
10
+ * Streamer#view method removed, use Database.view with block
11
+ * Database#changes method added (with streamer option)
12
+ * Added :allow_nil option when creating views
13
+
14
+ WARNING: If you depend on ordering by Time, this release may cause issues on old databases!
15
+ Either update your documents to use the new format (JSON standard), or use Javascript's Date.parse
16
+ method in your views. Also, use Time#utc to ensure universal ordering regardless of time
17
+ zone. (CouchRest Model does this automatically.)
18
+
1
19
  == 1.0.2
2
20
 
3
21
  * Minor enhancements
@@ -5,10 +5,10 @@ module CouchRest
5
5
  class Database
6
6
  attr_reader :server, :host, :name, :root, :uri
7
7
  attr_accessor :bulk_save_cache_limit
8
-
8
+
9
9
  # Create a CouchRest::Database adapter for the supplied CouchRest::Server
10
10
  # and database name.
11
- #
11
+ #
12
12
  # ==== Parameters
13
13
  # server<CouchRest::Server>:: database host
14
14
  # name<String>:: database name
@@ -19,77 +19,62 @@ module CouchRest
19
19
  @host = server.uri
20
20
  @uri = "/#{name.gsub('/','%2F')}"
21
21
  @root = host + uri
22
- @streamer = Streamer.new(self)
22
+ @streamer = Streamer.new
23
23
  @bulk_save_cache = []
24
24
  @bulk_save_cache_limit = 500 # must be smaller than the uuid count
25
25
  end
26
-
26
+
27
+ # == Database information and manipulation methods
28
+
27
29
  # returns the database's uri
28
30
  def to_s
29
31
  @root
30
32
  end
31
-
33
+
32
34
  # GET the database info from CouchDB
33
35
  def info
34
36
  CouchRest.get @root
35
37
  end
36
-
37
- # Query the <tt>_all_docs</tt> view. Accepts all the same arguments as view.
38
- def documents(params = {})
39
- keys = params.delete(:keys)
40
- url = CouchRest.paramify_url "#{@root}/_all_docs", params
41
- if keys
42
- CouchRest.post(url, {:keys => keys})
43
- else
44
- CouchRest.get url
45
- end
38
+
39
+ # Compact the database, removing old document revisions and optimizing space use.
40
+ def compact!
41
+ CouchRest.post "#{@root}/_compact"
46
42
  end
47
43
 
48
- # Query a CouchDB-Lucene search view
49
- def search(name, params={})
50
- # -> http://localhost:5984/yourdb/_fti/YourDesign/by_name?include_docs=true&q=plop*'
51
- url = CouchRest.paramify_url "#{root}/_fti/#{name}", params
52
- CouchRest.get url
44
+ # Create the database
45
+ def create!
46
+ bool = server.create_db(@name) rescue false
47
+ bool && true
53
48
  end
54
49
 
55
- # load a set of documents by passing an array of ids
56
- def get_bulk(ids)
57
- documents(:keys => ids, :include_docs => true)
50
+ # Delete and re create the database
51
+ def recreate!
52
+ delete!
53
+ create!
54
+ rescue RestClient::ResourceNotFound
55
+ ensure
56
+ create!
58
57
  end
59
- alias :bulk_load :get_bulk
60
-
61
- # POST a temporary view function to CouchDB for querying. This is not
62
- # recommended, as you don't get any performance benefit from CouchDB's
63
- # materialized views. Can be quite slow on large databases.
64
- def slow_view(funcs, params = {})
65
- keys = params.delete(:keys)
66
- funcs = funcs.merge({:keys => keys}) if keys
67
- url = CouchRest.paramify_url "#{@root}/_temp_view", params
68
- JSON.parse(RestClient.post(url, funcs.to_json, CouchRest.default_headers))
69
- end
70
-
71
- # backwards compatibility is a plus
72
- alias :temp_view :slow_view
73
-
74
- # Query a CouchDB view as defined by a <tt>_design</tt> document. Accepts
75
- # paramaters as described in http://wiki.apache.org/couchdb/HttpViewApi
76
- def view(name, params = {}, &block)
77
- keys = params.delete(:keys)
78
- name = name.split('/') # I think this will always be length == 2, but maybe not...
79
- dname = name.shift
80
- vname = name.join('/')
81
- url = CouchRest.paramify_url "#{@root}/_design/#{dname}/_view/#{vname}", params
82
- if keys
83
- CouchRest.post(url, {:keys => keys})
84
- else
85
- if block_given?
86
- @streamer.view("_design/#{dname}/_view/#{vname}", params, &block)
87
- else
88
- CouchRest.get url
89
- end
90
- end
58
+
59
+ # Replicates via "pulling" from another database to this database. Makes no attempt to deal with conflicts.
60
+ def replicate_from(other_db, continuous = false, create_target = false, doc_ids = nil)
61
+ replicate(other_db, continuous, :target => name, :create_target => create_target, :doc_ids => doc_ids)
62
+ end
63
+
64
+ # Replicates via "pushing" to another database. Makes no attempt to deal with conflicts.
65
+ def replicate_to(other_db, continuous = false, create_target = false, doc_ids = nil)
66
+ replicate(other_db, continuous, :source => name, :create_target => create_target, :doc_ids => doc_ids)
91
67
  end
92
-
68
+
69
+ # DELETE the database itself. This is not undoable and could be rather
70
+ # catastrophic. Use with care!
71
+ def delete!
72
+ CouchRest.delete @root
73
+ end
74
+
75
+
76
+ # == Retrieving and saving single documents
77
+
93
78
  # GET a document from CouchDB, by id. Returns a Ruby Hash.
94
79
  def get(id, params = {})
95
80
  slug = escape_docid(id)
@@ -104,37 +89,6 @@ module CouchRest
104
89
  doc.database = self
105
90
  doc
106
91
  end
107
-
108
- # GET an attachment directly from CouchDB
109
- def fetch_attachment(doc, name)
110
- uri = url_for_attachment(doc, name)
111
- RestClient.get uri, CouchRest.default_headers
112
- end
113
-
114
- # PUT an attachment directly to CouchDB
115
- def put_attachment(doc, name, file, options = {})
116
- docid = escape_docid(doc['_id'])
117
- uri = url_for_attachment(doc, name)
118
- JSON.parse(RestClient.put(uri, file, CouchRest.default_headers.merge(options)))
119
- end
120
-
121
- # DELETE an attachment directly from CouchDB
122
- def delete_attachment(doc, name, force=false)
123
- uri = url_for_attachment(doc, name)
124
- # this needs a rev
125
- begin
126
- CouchRest.delete(uri)
127
- rescue Exception => error
128
- if force
129
- # get over a 409
130
- doc = get(doc['_id'])
131
- uri = url_for_attachment(doc, name)
132
- CouchRest.delete(uri)
133
- else
134
- error
135
- end
136
- end
137
- end
138
92
 
139
93
  # Save a document to CouchDB. This will use the <tt>_id</tt> field from
140
94
  # the document as the id for PUT, or request a new UUID from CouchDB, if
@@ -194,7 +148,7 @@ module CouchRest
194
148
  end
195
149
  result
196
150
  end
197
-
151
+
198
152
  # Save a document to CouchDB in bulk mode. See #save_doc's +bulk+ argument.
199
153
  def bulk_save_doc(doc)
200
154
  save_doc(doc, true)
@@ -204,7 +158,7 @@ module CouchRest
204
158
  def batch_save_doc(doc)
205
159
  save_doc(doc, false, true)
206
160
  end
207
-
161
+
208
162
  # POST an array of documents to CouchDB. If any of the documents are
209
163
  # missing ids, supply one from the uuid cache.
210
164
  #
@@ -225,7 +179,7 @@ module CouchRest
225
179
  CouchRest.post "#{@root}/_bulk_docs", {:docs => docs}
226
180
  end
227
181
  alias :bulk_delete :bulk_save
228
-
182
+
229
183
  # DELETE the document from CouchDB that has the given <tt>_id</tt> and
230
184
  # <tt>_rev</tt>.
231
185
  #
@@ -241,7 +195,7 @@ module CouchRest
241
195
  slug = escape_docid(doc['_id'])
242
196
  CouchRest.delete "#{@root}/#{slug}?rev=#{doc['_rev']}"
243
197
  end
244
-
198
+
245
199
  # COPY an existing document to a new id. If the destination id currently exists, a rev must be provided.
246
200
  # <tt>dest</tt> can take one of two forms if overwriting: "id_to_overwrite?rev=revision" or the actual doc
247
201
  # hash with a '_rev' key
@@ -255,72 +209,135 @@ module CouchRest
255
209
  end
256
210
  CouchRest.copy "#{@root}/#{slug}", destination
257
211
  end
258
-
212
+
259
213
  # Updates the given doc by yielding the current state of the doc
260
- # and trying to update update_limit times. Returns the new doc
261
- # if the doc was successfully updated without hitting the limit
262
- def update_doc(doc_id, params = {}, update_limit=10)
214
+ # and trying to update update_limit times. Returns the doc
215
+ # if successfully updated without hitting the limit.
216
+ # If the limit is reached, the last execption will be raised.
217
+ def update_doc(doc_id, params = {}, update_limit = 10)
263
218
  resp = {'ok' => false}
264
- new_doc = nil
265
219
  last_fail = nil
266
220
 
267
221
  until resp['ok'] or update_limit <= 0
268
- doc = self.get(doc_id, params) # grab the doc
269
- new_doc = yield doc # give it to the caller to be updated
222
+ doc = self.get(doc_id, params)
223
+ yield doc
270
224
  begin
271
- resp = self.save_doc new_doc # try to PUT the updated doc into the db
225
+ resp = self.save_doc doc
272
226
  rescue RestClient::RequestFailed => e
273
227
  if e.http_code == 409 # Update collision
274
228
  update_limit -= 1
275
229
  last_fail = e
276
- else # some other error
230
+ else
277
231
  raise e
278
232
  end
279
233
  end
280
234
  end
281
235
 
282
236
  raise last_fail unless resp['ok']
283
- new_doc
237
+ doc
284
238
  end
285
-
286
- # Compact the database, removing old document revisions and optimizing space use.
287
- def compact!
288
- CouchRest.post "#{@root}/_compact"
239
+
240
+
241
+ # == View and multi-document based queries
242
+
243
+ # Query a CouchDB view as defined by a <tt>_design</tt> document. Accepts
244
+ # paramaters as described in http://wiki.apache.org/couchdb/HttpViewApi
245
+ def view(name, params = {}, payload = {}, &block)
246
+ payload[:keys] = params.delete(:keys) if params[:keys]
247
+ # Try recognising the name, otherwise assume already prepared
248
+ view_path = name =~ /^([^_].+?)\/(.*)$/ ? "_design/#{$1}/_view/#{$2}" : name
249
+ url = CouchRest.paramify_url "#{@root}/#{view_path}", params
250
+ if block_given?
251
+ if !payload.empty?
252
+ @streamer.post url, payload, &block
253
+ else
254
+ @streamer.get url, &block
255
+ end
256
+ else
257
+ if !payload.empty?
258
+ CouchRest.post url, payload
259
+ else
260
+ CouchRest.get url
261
+ end
262
+ end
289
263
  end
290
-
291
- # Create the database
292
- def create!
293
- bool = server.create_db(@name) rescue false
294
- bool && true
264
+
265
+ # POST a temporary view function to CouchDB for querying. This is not
266
+ # recommended, as you don't get any performance benefit from CouchDB's
267
+ # materialized views. Can be quite slow on large databases.
268
+ def temp_view(payload, params = {}, &block)
269
+ view('_temp_view', params, payload, &block)
295
270
  end
296
-
297
- # Delete and re create the database
298
- def recreate!
299
- delete!
300
- create!
301
- rescue RestClient::ResourceNotFound
302
- ensure
303
- create!
271
+ alias :slow_view :temp_view
272
+
273
+
274
+ # Query the <tt>_all_docs</tt> view. Accepts all the same arguments as view.
275
+ def all_docs(params = {}, payload = {}, &block)
276
+ view("_all_docs", params, payload, &block)
304
277
  end
278
+ alias :documents :all_docs
305
279
 
306
- # Replicates via "pulling" from another database to this database. Makes no attempt to deal with conflicts.
307
- def replicate_from(other_db, continuous = false, create_target = false)
308
- replicate(other_db, continuous, :target => name, :create_target => create_target)
280
+ # Query CouchDB's special <tt>_changes</tt> feed for the latest.
281
+ # All standard CouchDB options can be provided.
282
+ #
283
+ # Warning: sending :feed => 'continuous' will cause your code to block
284
+ # indefinetly while waiting for changes. You might want to look-up an
285
+ # alternative to this.
286
+ def changes(params = {}, payload = {}, &block)
287
+ view("_changes", params, payload, &block)
309
288
  end
310
289
 
311
- # Replicates via "pushing" to another database. Makes no attempt to deal with conflicts.
312
- def replicate_to(other_db, continuous = false, create_target = false)
313
- replicate(other_db, continuous, :source => name, :create_target => create_target)
290
+ # Query a CouchDB-Lucene search view
291
+ def fti(name, params={})
292
+ # -> http://localhost:5984/yourdb/_fti/YourDesign/by_name?include_docs=true&q=plop*'
293
+ view("_fti/#{name}", params)
314
294
  end
295
+ alias :search :fti
315
296
 
316
- # DELETE the database itself. This is not undoable and could be rather
317
- # catastrophic. Use with care!
318
- def delete!
319
- CouchRest.delete @root
297
+ # load a set of documents by passing an array of ids
298
+ def get_bulk(ids)
299
+ all_docs(:keys => ids, :include_docs => true)
300
+ end
301
+ alias :bulk_load :get_bulk
302
+
303
+
304
+ # == Handling attachments
305
+
306
+ # GET an attachment directly from CouchDB
307
+ def fetch_attachment(doc, name)
308
+ uri = url_for_attachment(doc, name)
309
+ RestClient.get uri, CouchRest.default_headers
310
+ end
311
+
312
+ # PUT an attachment directly to CouchDB
313
+ def put_attachment(doc, name, file, options = {})
314
+ docid = escape_docid(doc['_id'])
315
+ uri = url_for_attachment(doc, name)
316
+ JSON.parse(RestClient.put(uri, file, CouchRest.default_headers.merge(options)))
317
+ end
318
+
319
+ # DELETE an attachment directly from CouchDB
320
+ def delete_attachment(doc, name, force=false)
321
+ uri = url_for_attachment(doc, name)
322
+ # this needs a rev
323
+ begin
324
+ CouchRest.delete(uri)
325
+ rescue Exception => error
326
+ if force
327
+ # get over a 409
328
+ doc = get(doc['_id'])
329
+ uri = url_for_attachment(doc, name)
330
+ CouchRest.delete(uri)
331
+ else
332
+ error
333
+ end
334
+ end
320
335
  end
321
336
 
337
+
338
+
322
339
  private
323
-
340
+
324
341
  def replicate(other_db, continuous, options)
325
342
  raise ArgumentError, "must provide a CouchReset::Database" unless other_db.kind_of?(CouchRest::Database)
326
343
  raise ArgumentError, "must provide a target or source option" unless (options.key?(:target) || options.key?(:source))
@@ -331,6 +348,7 @@ module CouchRest
331
348
  payload[:target] = other_db.root
332
349
  end
333
350
  payload[:continuous] = continuous
351
+ payload[:doc_ids] = options[:doc_ids] if options[:doc_ids]
334
352
  CouchRest.post "#{@host}/_replicate", payload
335
353
  end
336
354
 
@@ -352,8 +370,8 @@ module CouchRest
352
370
  def url_for_attachment(doc, name)
353
371
  @root + uri_for_attachment(doc, name)
354
372
  end
355
-
356
- def escape_docid id
373
+
374
+ def escape_docid id
357
375
  /^_design\/(.*)/ =~ id ? "_design/#{CGI.escape($1)}" : CGI.escape(id)
358
376
  end
359
377
 
@@ -15,7 +15,8 @@ module CouchRest
15
15
  doc_keys = keys.collect{|k| "doc['#{k}']"}
16
16
  key_emit = doc_keys.length == 1 ? "#{doc_keys.first}" : "[#{doc_keys.join(', ')}]"
17
17
  guards = opts.delete(:guards) || []
18
- guards += doc_keys.map{|k| "(#{k} != null)"}
18
+ guards += doc_keys.map{|k| "(#{k} != null)"} unless opts.delete(:allow_nil)
19
+ guards << 'true' if guards.empty?
19
20
  map_function = <<-JAVASCRIPT
20
21
  function(doc) {
21
22
  if (#{guards.join(' && ')}) {
@@ -1,35 +1,42 @@
1
1
  module CouchRest
2
2
  class Streamer
3
- attr_accessor :db
4
- def initialize db
5
- @db = db
6
- end
7
-
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
- def view name, params = nil, &block
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
18
- url = CouchRest.paramify_url urlst, params
19
- # puts "stream #{url}"
3
+
4
+ attr_accessor :default_curl_opts
5
+
6
+ def initialize
7
+ self.default_curl_opts = "--silent --no-buffer --tcp-nodelay -H \"Content-Type: application/json\""
8
+ end
9
+
10
+ def view(*args)
11
+ raise "CouchRest::Streamer#view is depricated. Please use Database#view with block."
12
+ end
13
+
14
+ def get(url, &block)
15
+ open_pipe("curl #{default_curl_opts} \"#{url}\"", &block)
16
+ end
17
+
18
+ def post(url, params = {}, &block)
19
+ open_pipe("curl #{default_curl_opts} -d \"#{escape_quotes(params.to_json)}\" \"#{url}\"", &block)
20
+ end
21
+
22
+ protected
23
+
24
+ def escape_quotes(data)
25
+ data.gsub(/"/, '\"')
26
+ end
27
+
28
+ def open_pipe(cmd, &block)
20
29
  first = nil
21
- IO.popen("curl --silent \"#{url}\"") do |view|
22
- first = view.gets # discard header
23
- while line = view.gets
30
+ IO.popen(cmd) do |f|
31
+ first = f.gets # discard header
32
+ while line = f.gets
24
33
  row = parse_line(line)
25
34
  block.call row unless row.nil? # last line "}]" discarded
26
35
  end
27
36
  end
28
37
  parse_first(first)
29
38
  end
30
-
31
- private
32
-
39
+
33
40
  def parse_line line
34
41
  return nil unless line
35
42
  if /(\{.*\}),?/.match(line.chomp)
@@ -46,6 +53,6 @@ module CouchRest
46
53
  rescue
47
54
  nil
48
55
  end
49
-
56
+
50
57
  end
51
58
  end
@@ -1,22 +1,7 @@
1
- require 'timeout'
2
-
3
- # This file must be loaded after the JSON gem and any other library that beats up the Time class.
4
- class Time
5
- # This date format sorts lexicographically
6
- # and is compatible with Javascript's <tt>new Date(time_string)</tt> constructor.
7
- # Note this this format stores all dates in UTC so that collation
8
- # order is preserved. (There's no longer a need to set <tt>ENV['TZ'] = 'UTC'</tt>
9
- # in your application.)
10
-
11
- def to_json(options = nil)
12
- u = self.getutc
13
- %("#{u.strftime("%Y/%m/%d %H:%M:%S +0000")}")
14
- end
15
-
16
- end
17
-
18
1
  # Monkey patch for faster net/http io
19
2
  if RUBY_VERSION.to_f < 1.9
3
+ require 'timeout'
4
+
20
5
  class Net::BufferedIO #:nodoc:
21
6
  alias :old_rbuf_fill :rbuf_fill
22
7
  def rbuf_fill
@@ -38,5 +23,3 @@ if RUBY_VERSION.to_f < 1.9
38
23
  end
39
24
  end
40
25
  end
41
-
42
-
data/lib/couchrest.rb CHANGED
@@ -12,13 +12,8 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- require 'rubygems'
16
- gem 'rest-client', ">= 1.5.1"
17
- unless Kernel.const_defined?("JSON")
18
- gem 'json', '>= 1.4.6'
19
- require 'json'
20
- end
21
15
  require 'rest_client'
16
+ require 'json'
22
17
 
23
18
  # Not sure why this is required, so removed until a reason is found!
24
19
  $:.unshift File.dirname(__FILE__) unless
@@ -17,6 +17,12 @@ describe CouchRest::Database do
17
17
  end
18
18
  end
19
19
 
20
+ describe "#info" do
21
+ it "should request basic database data" do
22
+ @db.info['db_name'].should eql(TESTDB)
23
+ end
24
+ end
25
+
20
26
  describe "map query with _temp_view in Javascript" do
21
27
  before(:each) do
22
28
  @db.bulk_save([
@@ -146,6 +152,42 @@ describe CouchRest::Database do
146
152
  end
147
153
  rows.length.should == 2
148
154
  end
155
+ it "should accept a payload" do
156
+ rs = @db.view('first/test', {}, :keys => ["another", "wild"])
157
+ rs['rows'].length.should == 2
158
+ end
159
+ it "should accept a payload with block" do
160
+ rows = []
161
+ rs = @db.view('first/test', {:include_docs => true}, :keys => ["another", "wild"]) do |row|
162
+ rows << row
163
+ end
164
+ rows.length.should == 2
165
+ rows.first['doc']['another'].should_not be_empty
166
+ end
167
+ end
168
+
169
+ describe "#changes" do
170
+ # uses standard view method, so not much testing required
171
+ before(:each) do
172
+ [
173
+ {"wild" => "and random"},
174
+ {"mild" => "yet local"},
175
+ {"another" => ["set","of","keys"]}
176
+ ].each do |d|
177
+ @db.save_doc(d)
178
+ end
179
+ end
180
+
181
+ it "should produce a basic list of changes" do
182
+ c = @db.changes
183
+ c['results'].length.should eql(3)
184
+ end
185
+
186
+ it "should provide id of last document" do
187
+ c = @db.changes
188
+ doc = @db.get(c['results'].last['id'])
189
+ doc['another'].should_not be_empty
190
+ end
149
191
  end
150
192
 
151
193
  describe "GET (document by id) when the doc exists" do
@@ -561,7 +603,6 @@ describe CouchRest::Database do
561
603
  it "should work under normal conditions" do
562
604
  @db.update_doc @id do |doc|
563
605
  doc['upvotes'] += 1
564
- doc
565
606
  end
566
607
  @db.get(@id)['upvotes'].should == 11
567
608
  end
@@ -572,10 +613,9 @@ describe CouchRest::Database do
572
613
  conflicting_doc = @db.get @id
573
614
  conflicting_doc['upvotes'] += 1
574
615
  @db.save_doc conflicting_doc
575
-
616
+
576
617
  # then try saving it through the update
577
618
  doc['upvotes'] += 1
578
- doc
579
619
  end
580
620
  end.should raise_error(RestClient::RequestFailed)
581
621
  end
@@ -647,6 +687,7 @@ describe CouchRest::Database do
647
687
  ds['total_rows'].should == 5
648
688
  end
649
689
 
690
+ # This is redundant with the latest view code, but left in place for prosterity.
650
691
  describe "documents / _all_docs" do
651
692
  before(:each) do
652
693
  9.times do |i|
@@ -681,12 +722,11 @@ describe CouchRest::Database do
681
722
  end
682
723
 
683
724
 
684
- describe "compacting a database" do
725
+ describe "#compact" do
685
726
  it "should compact the database" do
686
727
  db = @cr.database('couchrest-test')
687
- # r =
688
- db.compact!
689
- # r['ok'].should == true
728
+ r = db.compact!
729
+ r['ok'].should == true
690
730
  end
691
731
  end
692
732
 
@@ -696,9 +736,8 @@ describe CouchRest::Database do
696
736
  end
697
737
  it "should delete the database" do
698
738
  db = @cr.database('couchrest-test')
699
- # r =
700
- db.delete!
701
- # r['ok'].should == true
739
+ r = db.delete!
740
+ r['ok'].should == true
702
741
  @cr.databases.should_not include('couchrest-test')
703
742
  end
704
743
  end
@@ -733,6 +772,20 @@ describe CouchRest::Database do
733
772
 
734
773
  it_should_behave_like "simply replicated"
735
774
  end
775
+
776
+ describe "with a specific doc" do
777
+ before(:each) do
778
+ @other_db.recreate!
779
+ @db.save_doc({'_id' => 'unreplicated_doc', 'some-value' => 'foo'})
780
+ @db.replicate_to @other_db, false, false, ['test_doc']
781
+ end
782
+
783
+ # should contain only replicated doc and not unreplicated doc
784
+ it_should_behave_like "simply replicated"
785
+ it "does not contain unreplicated doc" do
786
+ lambda { @other_db.get('unreplicated_doc') }.should raise_error(RestClient::ResourceNotFound)
787
+ end
788
+ end
736
789
 
737
790
  describe "implicitly creating target" do
738
791
  describe "via pulling" do
@@ -811,39 +864,38 @@ describe CouchRest::Database do
811
864
  end
812
865
  end
813
866
 
814
- describe "creating a database" do
867
+ describe "#create!" do
815
868
  before(:each) do
816
869
  @db = @cr.database('couchrest-test-db_to_create')
817
870
  @db.delete! if @cr.databases.include?('couchrest-test-db_to_create')
818
871
  end
819
-
872
+
820
873
  it "should just work fine" do
821
874
  @cr.databases.should_not include('couchrest-test-db_to_create')
822
875
  @db.create!
823
876
  @cr.databases.should include('couchrest-test-db_to_create')
824
877
  end
825
878
  end
826
-
827
- describe "recreating a database" do
879
+
880
+ describe "#recreate!" do
828
881
  before(:each) do
829
882
  @db = @cr.database('couchrest-test-db_to_create')
830
883
  @db2 = @cr.database('couchrest-test-db_to_recreate')
831
884
  @cr.databases.include?(@db.name) ? nil : @db.create!
832
885
  @cr.databases.include?(@db2.name) ? @db2.delete! : nil
833
886
  end
834
-
887
+
835
888
  it "should drop and recreate a database" do
836
889
  @cr.databases.should include(@db.name)
837
890
  @db.recreate!
838
891
  @cr.databases.should include(@db.name)
839
892
  end
840
-
893
+
841
894
  it "should recreate a db even tho it doesn't exist" do
842
895
  @cr.databases.should_not include(@db2.name)
843
896
  @db2.recreate!
844
897
  @cr.databases.should include(@db2.name)
845
898
  end
846
-
847
899
  end
848
900
 
849
901
  describe "searching a database" do
@@ -154,6 +154,26 @@ describe CouchRest::Design do
154
154
  end
155
155
  end
156
156
 
157
+ describe "a view with nil and 0 values and :allow_nil" do
158
+ before(:all) do
159
+ @db = reset_test_db!
160
+ @des = CouchRest::Design.new
161
+ @des.name = "test"
162
+ @des.view_by :code, :allow_nil => true
163
+ @des.database = @db
164
+ @des.save
165
+ @db.bulk_save([{"code" => "a", "age" => 2},
166
+ {"code" => nil, "age" => 4},{"code" => 0, "age" => 9}])
167
+ end
168
+ it "should work" do
169
+ res = @des.view :by_code
170
+ res["rows"][0]["key"].should == nil
171
+ res["rows"][1]["key"].should == 0
172
+ res["rows"][2]["key"].should == "a"
173
+ end
174
+ end
175
+
176
+
157
177
  describe "a view with a reduce function" do
158
178
  before(:all) do
159
179
  @db = reset_test_db!
@@ -6,7 +6,7 @@ describe CouchRest::Streamer do
6
6
  @db = @cr.database(TESTDB)
7
7
  @db.delete! rescue nil
8
8
  @db = @cr.create_db(TESTDB) rescue nil
9
- @streamer = CouchRest::Streamer.new(@db)
9
+ @streamer = CouchRest::Streamer.new()
10
10
  @docs = (1..1000).collect{|i| {:integer => i, :string => i.to_s}}
11
11
  @db.bulk_save(@docs)
12
12
  @db.save_doc({
@@ -18,35 +18,42 @@ describe CouchRest::Streamer do
18
18
  }
19
19
  })
20
20
  end
21
-
22
- it "should yield each row in a view" do
21
+
22
+ it "should raise error on #view as depricated" do
23
+ lambda { @streamer.view }.should raise_error(/depricated/)
24
+ end
25
+
26
+ it "should GET each row in a view" do
23
27
  count = 0
24
- sum = 0
25
- @streamer.view("_all_docs") do |row|
28
+ @streamer.get("#{@db.root}/_all_docs") do |row|
26
29
  count += 1
27
30
  end
28
31
  count.should == 1001
29
32
  end
30
33
 
31
- it "should accept several params" do
34
+ it "should GET each row in a view with params" do
32
35
  count = 0
33
- @streamer.view("_design/first/_view/test", :include_docs => true, :limit => 5) do |row|
36
+ @streamer.get("#{@db.root}/_all_docs?include_docs=true&limit=5") do |row|
34
37
  count += 1
35
38
  end
36
39
  count.should == 5
37
40
  end
38
41
 
39
- it "should accept both view formats" do
40
- count = 0
41
- @streamer.view("_design/first/_view/test") do |row|
42
- count += 1
42
+ it "should POST for each row in a view" do
43
+ # First grab a pair of IDs
44
+ ids = []
45
+ @streamer.get("#{@db.root}/_design/first/_view/test?limit=2") do |row|
46
+ ids << row['id']
43
47
  end
44
- count.should == 2000
45
48
  count = 0
46
- @streamer.view("first/test") do |row|
49
+ @streamer.post("#{@db.root}/_all_docs?include_docs=true", :keys => ids) do |row|
47
50
  count += 1
48
51
  end
49
- count.should == 2000
52
+ count.should == 2
53
+ end
54
+
55
+ it "should escape quotes" do
56
+ @streamer.send(:escape_quotes, "keys: [\"sams's test\"]").should eql("keys: [\\\"sams's test\\\"]")
50
57
  end
51
58
 
52
59
  end
metadata CHANGED
@@ -1,85 +1,82 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: couchrest
3
3
  version: !ruby/object:Gem::Version
4
- prerelease: false
4
+ prerelease: true
5
5
  segments:
6
- - 1
7
- - 0
8
- - 2
9
- version: 1.0.2
6
+ - 1
7
+ - 1
8
+ - 0
9
+ - pre
10
+ version: 1.1.0.pre
10
11
  platform: ruby
11
12
  authors:
12
- - J. Chris Anderson
13
- - Matt Aimonetti
14
- - Marcos Tapajos
15
- - Will Leinweber
16
- - Sam Lown
13
+ - J. Chris Anderson
14
+ - Matt Aimonetti
15
+ - Marcos Tapajos
16
+ - Will Leinweber
17
+ - Sam Lown
17
18
  autorequire:
18
19
  bindir: bin
19
20
  cert_chain: []
20
21
 
21
- date: 2011-03-13 00:00:00 +01:00
22
+ date: 2011-04-06 00:00:00 +02:00
22
23
  default_executable:
23
24
  dependencies:
24
- - !ruby/object:Gem::Dependency
25
- name: rest-client
26
- prerelease: false
27
- requirement: &id001 !ruby/object:Gem::Requirement
28
- none: false
29
- requirements:
30
- - - ~>
31
- - !ruby/object:Gem::Version
32
- segments:
33
- - 1
34
- - 6
35
- - 1
36
- version: 1.6.1
37
- type: :runtime
38
- version_requirements: *id001
39
- - !ruby/object:Gem::Dependency
40
- name: mime-types
41
- prerelease: false
42
- requirement: &id002 !ruby/object:Gem::Requirement
43
- none: false
44
- requirements:
45
- - - ~>
46
- - !ruby/object:Gem::Version
47
- segments:
48
- - 1
49
- - 15
50
- version: "1.15"
51
- type: :runtime
52
- version_requirements: *id002
53
- - !ruby/object:Gem::Dependency
54
- name: json
55
- prerelease: false
56
- requirement: &id003 !ruby/object:Gem::Requirement
57
- none: false
58
- requirements:
59
- - - ~>
60
- - !ruby/object:Gem::Version
61
- segments:
62
- - 1
63
- - 5
64
- - 1
65
- version: 1.5.1
66
- type: :runtime
67
- version_requirements: *id003
68
- - !ruby/object:Gem::Dependency
69
- name: rspec
70
- prerelease: false
71
- requirement: &id004 !ruby/object:Gem::Requirement
72
- none: false
73
- requirements:
74
- - - ~>
75
- - !ruby/object:Gem::Version
76
- segments:
77
- - 1
78
- - 3
79
- - 0
80
- version: 1.3.0
81
- type: :development
82
- version_requirements: *id004
25
+ - !ruby/object:Gem::Dependency
26
+ name: rest-client
27
+ prerelease: false
28
+ requirement: &id001 !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ~>
31
+ - !ruby/object:Gem::Version
32
+ segments:
33
+ - 1
34
+ - 6
35
+ - 1
36
+ version: 1.6.1
37
+ type: :runtime
38
+ version_requirements: *id001
39
+ - !ruby/object:Gem::Dependency
40
+ name: mime-types
41
+ prerelease: false
42
+ requirement: &id002 !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ~>
45
+ - !ruby/object:Gem::Version
46
+ segments:
47
+ - 1
48
+ - 15
49
+ version: "1.15"
50
+ type: :runtime
51
+ version_requirements: *id002
52
+ - !ruby/object:Gem::Dependency
53
+ name: json
54
+ prerelease: false
55
+ requirement: &id003 !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ~>
58
+ - !ruby/object:Gem::Version
59
+ segments:
60
+ - 1
61
+ - 5
62
+ - 1
63
+ version: 1.5.1
64
+ type: :runtime
65
+ version_requirements: *id003
66
+ - !ruby/object:Gem::Dependency
67
+ name: rspec
68
+ prerelease: false
69
+ requirement: &id004 !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ~>
72
+ - !ruby/object:Gem::Version
73
+ segments:
74
+ - 1
75
+ - 3
76
+ - 0
77
+ version: 1.3.0
78
+ type: :development
79
+ version_requirements: *id004
83
80
  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.
84
81
  email: jchris@apache.org
85
82
  executables: []
@@ -87,99 +84,112 @@ executables: []
87
84
  extensions: []
88
85
 
89
86
  extra_rdoc_files:
90
- - LICENSE
91
- - README.md
92
- - THANKS.md
87
+ - LICENSE
88
+ - README.md
89
+ - THANKS.md
93
90
  files:
94
- - .gitignore
95
- - Gemfile
96
- - Gemfile.lock
97
- - LICENSE
98
- - README.md
99
- - Rakefile
100
- - THANKS.md
101
- - couchrest.gemspec
102
- - examples/word_count/markov
103
- - examples/word_count/views/books/chunked-map.js
104
- - examples/word_count/views/books/united-map.js
105
- - examples/word_count/views/markov/chain-map.js
106
- - examples/word_count/views/markov/chain-reduce.js
107
- - examples/word_count/views/word_count/count-map.js
108
- - examples/word_count/views/word_count/count-reduce.js
109
- - examples/word_count/word_count.rb
110
- - examples/word_count/word_count_query.rb
111
- - examples/word_count/word_count_views.rb
112
- - history.txt
113
- - init.rb
114
- - lib/couchrest.rb
115
- - lib/couchrest/commands/generate.rb
116
- - lib/couchrest/commands/push.rb
117
- - lib/couchrest/database.rb
118
- - lib/couchrest/design.rb
119
- - lib/couchrest/document.rb
120
- - lib/couchrest/helper/attachments.rb
121
- - lib/couchrest/helper/pager.rb
122
- - lib/couchrest/helper/streamer.rb
123
- - lib/couchrest/helper/upgrade.rb
124
- - lib/couchrest/middlewares/logger.rb
125
- - lib/couchrest/monkeypatches.rb
126
- - lib/couchrest/response.rb
127
- - lib/couchrest/rest_api.rb
128
- - lib/couchrest/server.rb
129
- - lib/couchrest/support/inheritable_attributes.rb
130
- - spec/.gitignore
131
- - spec/couchrest/couchrest_spec.rb
132
- - spec/couchrest/database_spec.rb
133
- - spec/couchrest/design_spec.rb
134
- - spec/couchrest/document_spec.rb
135
- - spec/couchrest/helpers/pager_spec.rb
136
- - spec/couchrest/helpers/streamer_spec.rb
137
- - spec/couchrest/server_spec.rb
138
- - spec/fixtures/attachments/README
139
- - spec/fixtures/attachments/couchdb.png
140
- - spec/fixtures/attachments/test.html
141
- - spec/fixtures/views/lib.js
142
- - spec/fixtures/views/test_view/lib.js
143
- - spec/fixtures/views/test_view/only-map.js
144
- - spec/fixtures/views/test_view/test-map.js
145
- - spec/fixtures/views/test_view/test-reduce.js
146
- - spec/spec.opts
147
- - spec/spec_helper.rb
148
- - utils/remap.rb
149
- - utils/subset.rb
91
+ - .gitignore
92
+ - Gemfile
93
+ - LICENSE
94
+ - README.md
95
+ - Rakefile
96
+ - THANKS.md
97
+ - couchrest.gemspec
98
+ - examples/word_count/markov
99
+ - examples/word_count/views/books/chunked-map.js
100
+ - examples/word_count/views/books/united-map.js
101
+ - examples/word_count/views/markov/chain-map.js
102
+ - examples/word_count/views/markov/chain-reduce.js
103
+ - examples/word_count/views/word_count/count-map.js
104
+ - examples/word_count/views/word_count/count-reduce.js
105
+ - examples/word_count/word_count.rb
106
+ - examples/word_count/word_count_query.rb
107
+ - examples/word_count/word_count_views.rb
108
+ - history.txt
109
+ - init.rb
110
+ - lib/couchrest.rb
111
+ - lib/couchrest/commands/generate.rb
112
+ - lib/couchrest/commands/push.rb
113
+ - lib/couchrest/database.rb
114
+ - lib/couchrest/design.rb
115
+ - lib/couchrest/document.rb
116
+ - lib/couchrest/helper/attachments.rb
117
+ - lib/couchrest/helper/pager.rb
118
+ - lib/couchrest/helper/streamer.rb
119
+ - lib/couchrest/helper/upgrade.rb
120
+ - lib/couchrest/middlewares/logger.rb
121
+ - lib/couchrest/monkeypatches.rb
122
+ - lib/couchrest/response.rb
123
+ - lib/couchrest/rest_api.rb
124
+ - lib/couchrest/server.rb
125
+ - lib/couchrest/support/inheritable_attributes.rb
126
+ - spec/.gitignore
127
+ - spec/couchrest/couchrest_spec.rb
128
+ - spec/couchrest/database_spec.rb
129
+ - spec/couchrest/design_spec.rb
130
+ - spec/couchrest/document_spec.rb
131
+ - spec/couchrest/helpers/pager_spec.rb
132
+ - spec/couchrest/helpers/streamer_spec.rb
133
+ - spec/couchrest/server_spec.rb
134
+ - spec/fixtures/attachments/README
135
+ - spec/fixtures/attachments/couchdb.png
136
+ - spec/fixtures/attachments/test.html
137
+ - spec/fixtures/views/lib.js
138
+ - spec/fixtures/views/test_view/lib.js
139
+ - spec/fixtures/views/test_view/only-map.js
140
+ - spec/fixtures/views/test_view/test-map.js
141
+ - spec/fixtures/views/test_view/test-reduce.js
142
+ - spec/spec.opts
143
+ - spec/spec_helper.rb
144
+ - utils/remap.rb
145
+ - utils/subset.rb
150
146
  has_rdoc: true
151
147
  homepage: http://github.com/couchrest/couchrest
152
148
  licenses: []
153
149
 
154
150
  post_install_message:
155
151
  rdoc_options:
156
- - --charset=UTF-8
152
+ - --charset=UTF-8
157
153
  require_paths:
158
- - lib
154
+ - lib
159
155
  required_ruby_version: !ruby/object:Gem::Requirement
160
- none: false
161
156
  requirements:
162
- - - ">="
163
- - !ruby/object:Gem::Version
164
- segments:
165
- - 0
166
- version: "0"
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ segments:
160
+ - 0
161
+ version: "0"
167
162
  required_rubygems_version: !ruby/object:Gem::Requirement
168
- none: false
169
163
  requirements:
170
- - - ">"
171
- - !ruby/object:Gem::Version
172
- segments:
173
- - 1
174
- - 3
175
- - 1
176
- version: 1.3.1
164
+ - - ">"
165
+ - !ruby/object:Gem::Version
166
+ segments:
167
+ - 1
168
+ - 3
169
+ - 1
170
+ version: 1.3.1
177
171
  requirements: []
178
172
 
179
173
  rubyforge_project:
180
- rubygems_version: 1.3.7
174
+ rubygems_version: 1.3.6
181
175
  signing_key:
182
176
  specification_version: 3
183
177
  summary: Lean and RESTful interface to CouchDB.
184
- test_files: []
185
-
178
+ test_files:
179
+ - spec/couchrest/couchrest_spec.rb
180
+ - spec/couchrest/database_spec.rb
181
+ - spec/couchrest/design_spec.rb
182
+ - spec/couchrest/document_spec.rb
183
+ - spec/couchrest/helpers/pager_spec.rb
184
+ - spec/couchrest/helpers/streamer_spec.rb
185
+ - spec/couchrest/server_spec.rb
186
+ - spec/fixtures/attachments/README
187
+ - spec/fixtures/attachments/couchdb.png
188
+ - spec/fixtures/attachments/test.html
189
+ - spec/fixtures/views/lib.js
190
+ - spec/fixtures/views/test_view/lib.js
191
+ - spec/fixtures/views/test_view/only-map.js
192
+ - spec/fixtures/views/test_view/test-map.js
193
+ - spec/fixtures/views/test_view/test-reduce.js
194
+ - spec/spec.opts
195
+ - spec/spec_helper.rb
data/Gemfile.lock DELETED
@@ -1,24 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- couchrest (1.0.2)
5
- json (~> 1.5.1)
6
- mime-types (~> 1.15)
7
- rest-client (~> 1.6.1)
8
-
9
- GEM
10
- remote: http://rubygems.org/
11
- specs:
12
- json (1.5.1-java)
13
- mime-types (1.16)
14
- rest-client (1.6.1)
15
- mime-types (>= 1.16)
16
- rspec (1.3.1)
17
-
18
- PLATFORMS
19
- java
20
- ruby
21
-
22
- DEPENDENCIES
23
- couchrest!
24
- rspec (~> 1.3.0)