From 254eb20161265d0db5e7d4bf87a390647adc9c93 Mon Sep 17 00:00:00 2001 From: Chris Anderson Date: Tue, 14 Oct 2008 01:07:48 -0700 Subject: [PATCH] view blocks flow --- lib/couchrest/core/database.rb | 29 ++++++++++++----- lib/couchrest/core/model.rb | 31 +++++++++++++------ lib/couchrest/helper/streamer.rb | 14 ++++++--- spec/couchrest/core/database_spec.rb | 3 +- spec/couchrest/core/model_spec.rb | 23 ++++++++++++-- .../helpers}/file_manager_spec.rb | 2 +- .../fixtures/couchapp/attachments/index.html | 26 ++++++++++++++++ .../fixtures/couchapp/views/example-map.js | 8 +++++ .../fixtures/couchapp/views/example-reduce.js | 10 ++++++ spec/{ => couchrest/helpers}/pager_spec.rb | 2 +- spec/{ => couchrest/helpers}/streamer_spec.rb | 4 +-- 11 files changed, 124 insertions(+), 28 deletions(-) rename spec/{ => couchrest/helpers}/file_manager_spec.rb (99%) create mode 100644 spec/couchrest/helpers/fixtures/couchapp/attachments/index.html create mode 100644 spec/couchrest/helpers/fixtures/couchapp/views/example-map.js create mode 100644 spec/couchrest/helpers/fixtures/couchapp/views/example-reduce.js rename spec/{ => couchrest/helpers}/pager_spec.rb (98%) rename spec/{ => couchrest/helpers}/streamer_spec.rb (85%) diff --git a/lib/couchrest/core/database.rb b/lib/couchrest/core/database.rb index 3574bce..247410b 100644 --- a/lib/couchrest/core/database.rb +++ b/lib/couchrest/core/database.rb @@ -5,8 +5,9 @@ module CouchRest class Database attr_reader :server, :host, :name, :root - # Create a CouchRest::Database adapter for the supplied CouchRest::Server and database name. - # + # Create a CouchRest::Database adapter for the supplied CouchRest::Server + # and database name. + # # ==== Parameters # server:: database host # name:: database name @@ -40,7 +41,9 @@ module CouchRest end end - # POST a temporary view function to CouchDB for querying. This is not recommended, as you don't get any performance benefit from CouchDB's materialized views. Can be quite slow on large databases. + # POST a temporary view function to CouchDB for querying. This is not + # recommended, as you don't get any performance benefit from CouchDB's + # materialized views. Can be quite slow on large databases. def temp_view funcs, params = {} keys = params.delete(:keys) funcs = funcs.merge({:keys => keys}) if keys @@ -48,7 +51,8 @@ module CouchRest JSON.parse(RestClient.post(url, funcs.to_json, {"Content-Type" => 'application/json'})) end - # Query a CouchDB view as defined by a _design document. Accepts paramaters as described in http://wiki.apache.org/couchdb/HttpViewApi + # Query a CouchDB view as defined by a _design document. Accepts + # paramaters as described in http://wiki.apache.org/couchdb/HttpViewApi def view name, params = {}, &block keys = params.delete(:keys) url = CouchRest.paramify_url "#{@root}/_view/#{name}", params @@ -56,6 +60,7 @@ module CouchRest CouchRest.post(url, {:keys => keys}) else if block_given? + puts "streamer" @streamer.view(name, params, &block) else CouchRest.get url @@ -89,7 +94,12 @@ module CouchRest JSON.parse(RestClient.put(uri, file, options)) end - # Save a document to CouchDB. This will use the _id field from the document as the id for PUT, or request a new UUID from CouchDB, if no _id is present on the document. IDs are attached to documents on the client side because POST has the curious property of being automatically retried by proxies in the event of network segmentation and lost responses. + # Save a document to CouchDB. This will use the _id field from + # the document as the id for PUT, or request a new UUID from CouchDB, if + # no _id is present on the document. IDs are attached to + # documents on the client side because POST has the curious property of + # being automatically retried by proxies in the event of network + # segmentation and lost responses. def save doc if doc['_attachments'] doc['_attachments'] = encode_attachments(doc['_attachments']) @@ -107,7 +117,8 @@ module CouchRest end end - # POST an array of documents to CouchDB. If any of the documents are missing ids, supply one from the uuid cache. + # POST an array of documents to CouchDB. If any of the documents are + # missing ids, supply one from the uuid cache. def bulk_save docs ids, noids = docs.partition{|d|d['_id']} uuid_count = [noids.length, @server.uuid_batch_count].max @@ -118,13 +129,15 @@ module CouchRest CouchRest.post "#{@root}/_bulk_docs", {:docs => docs} end - # DELETE the document from CouchDB that has the given _id and _rev. + # DELETE the document from CouchDB that has the given _id and + # _rev. def delete doc slug = CGI.escape(doc['_id']) CouchRest.delete "#{@root}/#{slug}?rev=#{doc['_rev']}" end - # DELETE the database itself. This is not undoable and could be rather catastrophic. Use with care! + # DELETE the database itself. This is not undoable and could be rather + # catastrophic. Use with care! def delete! CouchRest.delete @root end diff --git a/lib/couchrest/core/model.rb b/lib/couchrest/core/model.rb index fbcd71a..7a9c62e 100644 --- a/lib/couchrest/core/model.rb +++ b/lib/couchrest/core/model.rb @@ -302,6 +302,9 @@ module CouchRest self.meta_class.instance_eval do define_method method_name do |*args| + # block = args.pop if args.last.is_a?(Proc) + block = nil + puts "block" if block_given? query = opts.merge(args[0] || {}) query[:raw] = true if query[:reduce] unless design_doc_fresh @@ -309,7 +312,7 @@ module CouchRest end raw = query.delete(:raw) view_name = "#{design_doc_slug}/#{method_name}" - fetch_view_with_docs(view_name, query, raw) + fetch_view_with_docs(view_name, query, raw, &block) end end end @@ -319,28 +322,38 @@ module CouchRest database.get("_design/#{design_doc_slug}") end + # Dispatches to any named view. + def view name, query={}, &block + name = name.to_s + view_name = "#{design_doc_slug}/#{name}" + puts view_name + fetch_view_with_docs(view_name, query, true, &block) + end + private - def fetch_view_with_docs name, opts, raw=false + def fetch_view_with_docs name, opts, raw=false, &block + if raw - fetch_view name, opts + fetch_view name, opts, &block else begin - view = fetch_view name, opts.merge({:include_docs => true}) - view['rows'].collect{|r|new(r['doc'])} + view = fetch_view name, opts.merge({:include_docs => true}), &block + view['rows'].collect{|r|new(r['doc'])} if view rescue # fallback for old versions of couchdb that don't # have include_docs support - view = fetch_view name, opts - view['rows'].collect{|r|new(database.get(r['id']))} + view = fetch_view name, opts, &block + view['rows'].collect{|r|new(database.get(r['id']))} if view end end end - def fetch_view view_name, opts + def fetch_view view_name, opts, &block retryable = true begin - database.view(view_name, opts) + puts "block" if block + database.view(view_name, opts, &block) # the design doc could have been deleted by a rouge process rescue RestClient::ResourceNotFound => e if retryable diff --git a/lib/couchrest/helper/streamer.rb b/lib/couchrest/helper/streamer.rb index 8a8065c..a237cf7 100644 --- a/lib/couchrest/helper/streamer.rb +++ b/lib/couchrest/helper/streamer.rb @@ -6,15 +6,21 @@ module CouchRest end # Stream a view, yielding one row at a time. Shells out to curl to keep RAM usage low when you have millions of rows. - def view name, params = nil + def view name, params = nil, &block urlst = /^_/.match(name) ? "#{@db.root}/#{name}" : "#{@db.root}/_view/#{name}" url = CouchRest.paramify_url urlst, params + first = nil IO.popen("curl --silent #{url}") do |view| - view.gets # discard header - while row = parse_line(view.gets) - yield row + first = view.gets # discard header + # puts first + while line = view.gets + # puts line + row = parse_line(line) + block.call row end end + # parse_line(line) + first end private diff --git a/spec/couchrest/core/database_spec.rb b/spec/couchrest/core/database_spec.rb index 640d789..a21e17c 100644 --- a/spec/couchrest/core/database_spec.rb +++ b/spec/couchrest/core/database_spec.rb @@ -122,7 +122,8 @@ describe CouchRest::Database do rs = @db.view('first/test', :include_docs => true) do |row| rows << row end - rows.length.should == 3 + rows.length.should == 4 + rs.should == 'a parsed thing. not that easy.' end end diff --git a/spec/couchrest/core/model_spec.rb b/spec/couchrest/core/model_spec.rb index bf7f5d9..981ad5c 100644 --- a/spec/couchrest/core/model_spec.rb +++ b/spec/couchrest/core/model_spec.rb @@ -419,17 +419,36 @@ describe CouchRest::Model do @db = @cr.create_db(TESTDB) rescue nil Course.new(:title => 'aaa').save Course.new(:title => 'bbb').save + Course.new(:title => 'ddd').save + Course.new(:title => 'eee').save end it "should make the design doc upon first query" do Course.by_title doc = Course.design_doc doc['views']['all']['map'].should include('Course') end + it "should can query via view" do + # register methods with method-missing, for local dispatch. method + # missing lookup table, no heuristics. + view = Course.view :by_title + designed = Course.by_title :raw => true + view.should == designed + end it "should get them" do rs = Course.by_title - rs.length.should == 2 + rs.length.should == 4 end - end + it "should yield" do + courses = [] + puts "Course.view(:by_title)" + rs = Course.by_title # remove me + Course.view(:by_title) do |course| + # puts "course" + courses << course + end + courses[0]["key"].should =='aaa' + end +end describe "a ducktype view" do before(:all) do diff --git a/spec/file_manager_spec.rb b/spec/couchrest/helpers/file_manager_spec.rb similarity index 99% rename from spec/file_manager_spec.rb rename to spec/couchrest/helpers/file_manager_spec.rb index 9feb7e1..65a5118 100644 --- a/spec/file_manager_spec.rb +++ b/spec/couchrest/helpers/file_manager_spec.rb @@ -1,4 +1,4 @@ -require File.dirname(__FILE__) + '/spec_helper' +require File.dirname(__FILE__) + '/../../spec_helper' describe CouchRest::FileManager do before(:all) do diff --git a/spec/couchrest/helpers/fixtures/couchapp/attachments/index.html b/spec/couchrest/helpers/fixtures/couchapp/attachments/index.html new file mode 100644 index 0000000..8b3c2b7 --- /dev/null +++ b/spec/couchrest/helpers/fixtures/couchapp/attachments/index.html @@ -0,0 +1,26 @@ + + + + Generated CouchApp + + + +

Generated CouchApp

+
    + + + + + + \ No newline at end of file diff --git a/spec/couchrest/helpers/fixtures/couchapp/views/example-map.js b/spec/couchrest/helpers/fixtures/couchapp/views/example-map.js new file mode 100644 index 0000000..878684d --- /dev/null +++ b/spec/couchrest/helpers/fixtures/couchapp/views/example-map.js @@ -0,0 +1,8 @@ +// an example map function, emits the doc id +// and the list of keys it contains + +function(doc) { + var k, keys = [] + for (k in doc) keys.push(k); + emit(doc._id, keys); +}; diff --git a/spec/couchrest/helpers/fixtures/couchapp/views/example-reduce.js b/spec/couchrest/helpers/fixtures/couchapp/views/example-reduce.js new file mode 100644 index 0000000..23eea60 --- /dev/null +++ b/spec/couchrest/helpers/fixtures/couchapp/views/example-reduce.js @@ -0,0 +1,10 @@ +// example reduce function to count the +// number of rows in a given key range. + +function(keys, values, rereduce) { + if (rereduce) { + return sum(values); + } else { + return values.length; + } +}; \ No newline at end of file diff --git a/spec/pager_spec.rb b/spec/couchrest/helpers/pager_spec.rb similarity index 98% rename from spec/pager_spec.rb rename to spec/couchrest/helpers/pager_spec.rb index f7662bf..8420242 100644 --- a/spec/pager_spec.rb +++ b/spec/couchrest/helpers/pager_spec.rb @@ -1,4 +1,4 @@ -require File.dirname(__FILE__) + '/spec_helper' +require File.dirname(__FILE__) + '/../../spec_helper' describe CouchRest::Pager do before(:all) do diff --git a/spec/streamer_spec.rb b/spec/couchrest/helpers/streamer_spec.rb similarity index 85% rename from spec/streamer_spec.rb rename to spec/couchrest/helpers/streamer_spec.rb index da0b5f0..5479390 100644 --- a/spec/streamer_spec.rb +++ b/spec/couchrest/helpers/streamer_spec.rb @@ -1,4 +1,4 @@ -require File.dirname(__FILE__) + '/spec_helper' +require File.dirname(__FILE__) + '/../../spec_helper' describe CouchRest::Streamer do before(:all) do @@ -17,7 +17,7 @@ describe CouchRest::Streamer do @streamer.view("_all_docs") do |row| count += 1 end - count.should == 1000 + count.should == 1001 end end \ No newline at end of file