view blocks flow

This commit is contained in:
Chris Anderson 2008-10-14 01:07:48 -07:00
parent 54a0afdf8e
commit 254eb20161
11 changed files with 124 additions and 28 deletions

View file

@ -5,8 +5,9 @@ module CouchRest
class Database class Database
attr_reader :server, :host, :name, :root 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 # ==== Parameters
# server<CouchRest::Server>:: database host # server<CouchRest::Server>:: database host
# name<String>:: database name # name<String>:: database name
@ -40,7 +41,9 @@ module CouchRest
end end
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 = {} def temp_view funcs, params = {}
keys = params.delete(:keys) keys = params.delete(:keys)
funcs = funcs.merge({:keys => keys}) if 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'})) JSON.parse(RestClient.post(url, funcs.to_json, {"Content-Type" => 'application/json'}))
end end
# Query a CouchDB view as defined by a <tt>_design</tt> document. Accepts paramaters as described in http://wiki.apache.org/couchdb/HttpViewApi # Query a CouchDB view as defined by a <tt>_design</tt> document. Accepts
# paramaters as described in http://wiki.apache.org/couchdb/HttpViewApi
def view name, params = {}, &block def view name, params = {}, &block
keys = params.delete(:keys) keys = params.delete(:keys)
url = CouchRest.paramify_url "#{@root}/_view/#{name}", params url = CouchRest.paramify_url "#{@root}/_view/#{name}", params
@ -56,6 +60,7 @@ module CouchRest
CouchRest.post(url, {:keys => keys}) CouchRest.post(url, {:keys => keys})
else else
if block_given? if block_given?
puts "streamer"
@streamer.view(name, params, &block) @streamer.view(name, params, &block)
else else
CouchRest.get url CouchRest.get url
@ -89,7 +94,12 @@ module CouchRest
JSON.parse(RestClient.put(uri, file, options)) JSON.parse(RestClient.put(uri, file, options))
end end
# Save a document to CouchDB. This will use the <tt>_id</tt> field from the document as the id for PUT, or request a new UUID from CouchDB, if no <tt>_id</tt> 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 <tt>_id</tt> field from
# the document as the id for PUT, or request a new UUID from CouchDB, if
# no <tt>_id</tt> 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 def save doc
if doc['_attachments'] if doc['_attachments']
doc['_attachments'] = encode_attachments(doc['_attachments']) doc['_attachments'] = encode_attachments(doc['_attachments'])
@ -107,7 +117,8 @@ module CouchRest
end end
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 def bulk_save docs
ids, noids = docs.partition{|d|d['_id']} ids, noids = docs.partition{|d|d['_id']}
uuid_count = [noids.length, @server.uuid_batch_count].max uuid_count = [noids.length, @server.uuid_batch_count].max
@ -118,13 +129,15 @@ module CouchRest
CouchRest.post "#{@root}/_bulk_docs", {:docs => docs} CouchRest.post "#{@root}/_bulk_docs", {:docs => docs}
end end
# DELETE the document from CouchDB that has the given <tt>_id</tt> and <tt>_rev</tt>. # DELETE the document from CouchDB that has the given <tt>_id</tt> and
# <tt>_rev</tt>.
def delete doc def delete doc
slug = CGI.escape(doc['_id']) slug = CGI.escape(doc['_id'])
CouchRest.delete "#{@root}/#{slug}?rev=#{doc['_rev']}" CouchRest.delete "#{@root}/#{slug}?rev=#{doc['_rev']}"
end 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! def delete!
CouchRest.delete @root CouchRest.delete @root
end end

View file

@ -302,6 +302,9 @@ module CouchRest
self.meta_class.instance_eval do self.meta_class.instance_eval do
define_method method_name do |*args| 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 = opts.merge(args[0] || {})
query[:raw] = true if query[:reduce] query[:raw] = true if query[:reduce]
unless design_doc_fresh unless design_doc_fresh
@ -309,7 +312,7 @@ module CouchRest
end end
raw = query.delete(:raw) raw = query.delete(:raw)
view_name = "#{design_doc_slug}/#{method_name}" 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 end
end end
@ -319,28 +322,38 @@ module CouchRest
database.get("_design/#{design_doc_slug}") database.get("_design/#{design_doc_slug}")
end 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 private
def fetch_view_with_docs name, opts, raw=false def fetch_view_with_docs name, opts, raw=false, &block
if raw if raw
fetch_view name, opts fetch_view name, opts, &block
else else
begin begin
view = fetch_view name, opts.merge({:include_docs => true}) view = fetch_view name, opts.merge({:include_docs => true}), &block
view['rows'].collect{|r|new(r['doc'])} view['rows'].collect{|r|new(r['doc'])} if view
rescue rescue
# fallback for old versions of couchdb that don't # fallback for old versions of couchdb that don't
# have include_docs support # have include_docs support
view = fetch_view name, opts view = fetch_view name, opts, &block
view['rows'].collect{|r|new(database.get(r['id']))} view['rows'].collect{|r|new(database.get(r['id']))} if view
end end
end end
end end
def fetch_view view_name, opts def fetch_view view_name, opts, &block
retryable = true retryable = true
begin 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 # the design doc could have been deleted by a rouge process
rescue RestClient::ResourceNotFound => e rescue RestClient::ResourceNotFound => e
if retryable if retryable

View file

@ -6,15 +6,21 @@ module CouchRest
end end
# 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. # 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.
def view name, params = nil def view name, params = nil, &block
urlst = /^_/.match(name) ? "#{@db.root}/#{name}" : "#{@db.root}/_view/#{name}" urlst = /^_/.match(name) ? "#{@db.root}/#{name}" : "#{@db.root}/_view/#{name}"
url = CouchRest.paramify_url urlst, params url = CouchRest.paramify_url urlst, params
first = nil
IO.popen("curl --silent #{url}") do |view| IO.popen("curl --silent #{url}") do |view|
view.gets # discard header first = view.gets # discard header
while row = parse_line(view.gets) # puts first
yield row while line = view.gets
# puts line
row = parse_line(line)
block.call row
end end
end end
# parse_line(line)
first
end end
private private

View file

@ -122,7 +122,8 @@ describe CouchRest::Database do
rs = @db.view('first/test', :include_docs => true) do |row| rs = @db.view('first/test', :include_docs => true) do |row|
rows << row rows << row
end end
rows.length.should == 3 rows.length.should == 4
rs.should == 'a parsed thing. not that easy.'
end end
end end

View file

@ -419,17 +419,36 @@ describe CouchRest::Model do
@db = @cr.create_db(TESTDB) rescue nil @db = @cr.create_db(TESTDB) rescue nil
Course.new(:title => 'aaa').save Course.new(:title => 'aaa').save
Course.new(:title => 'bbb').save Course.new(:title => 'bbb').save
Course.new(:title => 'ddd').save
Course.new(:title => 'eee').save
end end
it "should make the design doc upon first query" do it "should make the design doc upon first query" do
Course.by_title Course.by_title
doc = Course.design_doc doc = Course.design_doc
doc['views']['all']['map'].should include('Course') doc['views']['all']['map'].should include('Course')
end 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 it "should get them" do
rs = Course.by_title rs = Course.by_title
rs.length.should == 2 rs.length.should == 4
end 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 describe "a ducktype view" do
before(:all) do before(:all) do

View file

@ -1,4 +1,4 @@
require File.dirname(__FILE__) + '/spec_helper' require File.dirname(__FILE__) + '/../../spec_helper'
describe CouchRest::FileManager do describe CouchRest::FileManager do
before(:all) do before(:all) do

View file

@ -0,0 +1,26 @@
<!DOCTYPE html>
<html>
<head>
<title>Generated CouchApp</title>
<link rel="stylesheet" href="screen.css" type="text/css">
</head>
<body>
<h1>Generated CouchApp</h1>
<ul id="view"></ul>
</body>
<script src="/_utils/script/json2.js"></script>
<script src="/_utils/script/jquery.js?1.2.6"></script>
<script src="/_utils/script/jquery.couch.js?0.8.0"></script>
<script type="text/javascript" charset="utf-8">
$(function() {
var dbname = document.location.href.split('/')[3];
var design = unescape(document.location.href.split('/')[4]).split('/')[1];
var DB = $.couch.db(dbname);
DB.view(design+"/example",{success: function(json) {
$("#view").html(json.rows.map(function(row) {
return '<li>'+row.key+'</li>';
}).join(''));
}});
});
</script>
</html>

View file

@ -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);
};

View file

@ -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;
}
};

View file

@ -1,4 +1,4 @@
require File.dirname(__FILE__) + '/spec_helper' require File.dirname(__FILE__) + '/../../spec_helper'
describe CouchRest::Pager do describe CouchRest::Pager do
before(:all) do before(:all) do

View file

@ -1,4 +1,4 @@
require File.dirname(__FILE__) + '/spec_helper' require File.dirname(__FILE__) + '/../../spec_helper'
describe CouchRest::Streamer do describe CouchRest::Streamer do
before(:all) do before(:all) do
@ -17,7 +17,7 @@ describe CouchRest::Streamer do
@streamer.view("_all_docs") do |row| @streamer.view("_all_docs") do |row|
count += 1 count += 1
end end
count.should == 1000 count.should == 1001
end end
end end