Navigation Menu

Skip to content

Commit

Permalink
Allow create design documents from IO and String
Browse files Browse the repository at this point in the history
Change-Id: I125b377d76c2b3bdce422a26df21e62e3b0c9718
Reviewed-on: http://review.couchbase.org/9276
Tested-by: Sergey Avseyev <sergey.avseyev@gmail.com>
Reviewed-by: Matt Ingenthron <matt@couchbase.com>
  • Loading branch information
avsej committed Aug 31, 2011
1 parent 61777c8 commit d90d8ee
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 11 deletions.
20 changes: 17 additions & 3 deletions README.markdown
Expand Up @@ -67,9 +67,23 @@ example, store a couple of posts using memcached API:
:date => '2009/01/15 15:52:20'}
c.all_docs.count #=> 3

Now let's create design doc with sample view:

c.save_design_doc('blog', 'recent_posts' => {'map' => 'function(doc){if(doc.date && doc.title){emit(doc.date, doc.title);}}'})
Now let's create design doc with sample view and save it in file
'blog.json':

{
"_id": "_design/blog",
"language": "javascript",
"views": {
"recent_posts": {
"map": "function(doc){if(doc.date && doc.title){emit(doc.date, doc.title);}}"
}
}
}

This design document could be loaded into the database like this (also you can
pass the ruby Hash or String with JSON encoded document):

c.save_design_doc(File.open('blog.json'))

To execute view you need to fetch it from design document `_design/blog`:

Expand Down
51 changes: 48 additions & 3 deletions lib/couchbase/couchdb.rb
Expand Up @@ -17,11 +17,20 @@

module Couchbase
module Couchdb

# Initializes CouchDB related part of connection.
#
# @param [ String ] pool_uri Couchbase pool URI.
#
# @param [ Hash ] options Connection options. This is the hash the client
# passed to Couchbase.new to start the session
def initialize(pool_uri, options = {})
super
end

# Fetch design docs stored in current bucket
#
# @return [ Hash ]
def design_docs
docs = http_get("#{next_node.couch_api_base}/_all_docs",
:params => {:startkey => "_design/", :endkey => "_design0", :include_docs => true})
Expand All @@ -36,22 +45,37 @@ def design_docs
end

# Update or create design doc with supplied views
def save_design_doc(id, views, language = 'javascript')
doc = Document.wrap(self, '_id' => "_design/#{id}", 'language' => language, 'views' => views)
#
# @param [ Hash, IO, String ] data The source object containing JSON
# encoded design document. It must have
# <tt>_id</tt> key set, this key should
# start with <tt>_design/</tt>.
#
# @return [ Couchbase::Document ] instance
def save_design_doc(data)
doc = parse_design_document(data)
rv = http_put("#{next_node.couch_api_base}/#{doc['_id']}", {}, doc)
doc['_rev'] = rv['rev']
doc
end

# Fetch all documents from the bucket.
#
# @param [ Hash ] params Params for Couchdb +/_all_docs+ query
# @param [ Hash ] params Params for CouchDB <tt>/_all_docs</tt> query
#
# @return [ Couchbase::View ] View object
def all_docs(params = {})
View.new(self, "#{next_node.couch_api_base}/_all_docs", params)
end

# Delete design doc with given id and revision.
#
# @param [ String ] id Design document id. The method will prepend
# <tt>_design/</tt> prefix if it's absent.
#
# @param [ String ] rev Document revision. It uses latest revision if
# <tt>rev</tt> parameter is nil.
#
def delete_design_doc(id, rev = nil)
if rev.nil?
ddoc = design_docs[id]
Expand All @@ -63,5 +87,26 @@ def delete_design_doc(id, rev = nil)
end
http_delete("#{next_node.couch_api_base}/#{id}", :params => {:rev => rev})
end

protected

def parse_design_document(doc)
data = case doc
when String
Yajl::Parser.parse(doc)
when IO
Yajl::Parser.parse(doc.read)
when Hash
doc
else
raise ArgumentError, "Document should be Hash, String or IO instance"
end

if data['_id'].to_s !~ /^_design\//
raise ArgumentError, "'_id' key must be set and start with '_design/'."
end
data['language'] ||= 'javascript'
Document.wrap(self, data)
end
end
end
9 changes: 9 additions & 0 deletions test/support/sample_design_doc.json
@@ -0,0 +1,9 @@
{
"_id": "_design/test_it_creates_design_doc_from_io",
"language": "javascript",
"views": {
"all": {
"map": "function(doc){ if(doc.msg){ emit(doc._id, doc.msg) } }"
}
}
}
47 changes: 46 additions & 1 deletion test/test_couchdb.rb
Expand Up @@ -24,14 +24,59 @@ def test_that_it_could_create_doc_using_memcached_api
@bucket.delete_design_doc(test_id)

@bucket[test_id] = {'msg' => 'hello world'}
@bucket.save_design_doc(test_id, 'all' => {'map' => 'function(doc){if(doc.msg){emit(doc._id, doc.msg)}}'})
@bucket.save_design_doc('_id' => "_design/#{test_id}",
'views' => {'all' => {'map' => 'function(doc){if(doc.msg){emit(doc._id, doc.msg)}}'}})
assert_operation_completed { database_ready(@bucket) }
ddoc = @bucket.design_docs[test_id]
assert ddoc, "design document '_design/#{test_id}' not found"
doc = ddoc.all.detect{|doc| doc['id'] == test_id && doc['value'] == 'hello world'}
assert doc, "object '#{test_id}' not found"
end

def test_it_creates_design_doc_from_string
doc = <<-EOD
{
"_id": "_design/#{test_id}",
"language": "javascript",
"views": {
"all": {
"map": "function(doc){ if(doc.msg){ emit(doc._id, doc.msg) } }"
}
}
}
EOD

@bucket.delete_design_doc(test_id)
@bucket.save_design_doc(doc)
assert_operation_completed { database_ready(@bucket) }
assert @bucket.design_docs[test_id], "design document '_design/#{test_id}' not found"
end

def test_it_creates_design_doc_from_io
doc = File.open(File.join(File.dirname(__FILE__), 'support', 'sample_design_doc.json'))
@bucket.delete_design_doc(test_id)
@bucket.save_design_doc(doc)
assert_operation_completed { database_ready(@bucket) }
assert @bucket.design_docs[test_id], "design document '_design/#{test_id}' not found"
end

def test_it_creates_design_doc_from_hash
doc = {
"_id" => "_design/#{test_id}",
"language" => "javascript",
"views" => {
"all" => {
"map" => "function(doc){ if(doc.msg){ emit(doc._id, doc.msg) } }"
}
}
}

@bucket.delete_design_doc(test_id)
@bucket.save_design_doc(doc)
assert_operation_completed { database_ready(@bucket) }
assert @bucket.design_docs[test_id], "design document '_design/#{test_id}' not found"
end

protected

def test_id
Expand Down
10 changes: 6 additions & 4 deletions test/test_view.rb
Expand Up @@ -32,10 +32,12 @@ def populate_database
end

connection.delete_design_doc('test_view')
connection.save_design_doc('test_view',
'all' => {'map' => 'function(doc){if(doc.counter){emit(doc._id, doc)}}'},
'sum' => {'map' => 'function(doc){if(doc.counter){emit(doc.toy, doc.counter)}}',
'reduce' => 'function(keys,values,rereduce){return sum(values)}'})
connection.save_design_doc('_id' => '_design/test_view',
'views' => {
'all' => {'map' => 'function(doc){if(doc.counter){emit(doc._id, doc)}}'},
'sum' => {'map' => 'function(doc){if(doc.counter){emit(doc.toy, doc.counter)}}',
'reduce' => 'function(keys,values,rereduce){return sum(values)}'}})

assert connection.design_docs['test_view']

assert_operation_completed do
Expand Down

0 comments on commit d90d8ee

Please sign in to comment.