couchrest 1.0.2 → 1.1.0.pre

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