couchrest 0.12.2
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/LICENSE +176 -0
- data/README.md +69 -0
- data/Rakefile +103 -0
- data/THANKS.md +18 -0
- data/bin/couchdir +20 -0
- data/examples/model/example.rb +138 -0
- data/examples/word_count/markov +38 -0
- data/examples/word_count/views/books/chunked-map.js +3 -0
- data/examples/word_count/views/books/united-map.js +1 -0
- data/examples/word_count/views/markov/chain-map.js +6 -0
- data/examples/word_count/views/markov/chain-reduce.js +7 -0
- data/examples/word_count/views/word_count/count-map.js +6 -0
- data/examples/word_count/views/word_count/count-reduce.js +3 -0
- data/examples/word_count/word_count.rb +67 -0
- data/examples/word_count/word_count_query.rb +39 -0
- data/lib/couchrest/commands/generate.rb +71 -0
- data/lib/couchrest/commands/push.rb +103 -0
- data/lib/couchrest/core/database.rb +239 -0
- data/lib/couchrest/core/design.rb +89 -0
- data/lib/couchrest/core/document.rb +63 -0
- data/lib/couchrest/core/model.rb +607 -0
- data/lib/couchrest/core/server.rb +51 -0
- data/lib/couchrest/core/view.rb +4 -0
- data/lib/couchrest/helper/pager.rb +103 -0
- data/lib/couchrest/helper/streamer.rb +44 -0
- data/lib/couchrest/monkeypatches.rb +24 -0
- data/lib/couchrest.rb +140 -0
- data/spec/couchrest/core/couchrest_spec.rb +201 -0
- data/spec/couchrest/core/database_spec.rb +630 -0
- data/spec/couchrest/core/design_spec.rb +131 -0
- data/spec/couchrest/core/document_spec.rb +130 -0
- data/spec/couchrest/core/model_spec.rb +855 -0
- data/spec/couchrest/helpers/pager_spec.rb +122 -0
- data/spec/couchrest/helpers/streamer_spec.rb +23 -0
- data/spec/fixtures/attachments/README +3 -0
- data/spec/fixtures/attachments/couchdb.png +0 -0
- data/spec/fixtures/attachments/test.html +11 -0
- data/spec/fixtures/views/lib.js +3 -0
- data/spec/fixtures/views/test_view/lib.js +3 -0
- data/spec/fixtures/views/test_view/only-map.js +4 -0
- data/spec/fixtures/views/test_view/test-map.js +3 -0
- data/spec/fixtures/views/test_view/test-reduce.js +3 -0
- data/spec/spec.opts +6 -0
- data/spec/spec_helper.rb +20 -0
- data/utils/remap.rb +27 -0
- data/utils/subset.rb +30 -0
- metadata +156 -0
@@ -0,0 +1,39 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../../couchrest'
|
2
|
+
|
3
|
+
couch = CouchRest.new("http://127.0.0.1:5984")
|
4
|
+
db = couch.database('word-count-example')
|
5
|
+
|
6
|
+
puts "Now that we've parsed all those books into CouchDB, the queries we can run are incredibly flexible."
|
7
|
+
puts "\nThe simplest query we can run is the total word count for all words in all documents:"
|
8
|
+
|
9
|
+
puts db.view('word_count/count').inspect
|
10
|
+
|
11
|
+
puts "\nWe can also narrow the query down to just one word, across all documents. Here is the count for 'flight' in all three books:"
|
12
|
+
|
13
|
+
word = 'flight'
|
14
|
+
params = {
|
15
|
+
:startkey => [word],
|
16
|
+
:endkey => [word,'Z']
|
17
|
+
}
|
18
|
+
|
19
|
+
puts db.view('word_count/count',params).inspect
|
20
|
+
|
21
|
+
puts "\nWe scope the query using startkey and endkey params to take advantage of CouchDB's collation ordering. Here are the params for the last query:"
|
22
|
+
puts params.inspect
|
23
|
+
|
24
|
+
puts "\nWe can also count words on a per-title basis."
|
25
|
+
|
26
|
+
title = 'da-vinci'
|
27
|
+
params = {
|
28
|
+
:key => [word, title]
|
29
|
+
}
|
30
|
+
|
31
|
+
puts db.view('word_count/count',params).inspect
|
32
|
+
|
33
|
+
|
34
|
+
puts "\nHere are the params for 'flight' in the da-vinci book:"
|
35
|
+
puts params.inspect
|
36
|
+
puts
|
37
|
+
puts 'The url looks like this:'
|
38
|
+
puts 'http://127.0.0.1:5984/word-count-example/_view/word_count/count?key=["flight","da-vinci"]'
|
39
|
+
puts "\nTry dropping that in your browser..."
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
module CouchRest
|
4
|
+
module Commands
|
5
|
+
module Generate
|
6
|
+
|
7
|
+
def self.run(options)
|
8
|
+
directory = options[:directory]
|
9
|
+
design_names = options[:trailing_args]
|
10
|
+
|
11
|
+
FileUtils.mkdir_p(directory)
|
12
|
+
filename = File.join(directory, "lib.js")
|
13
|
+
self.write(filename, <<-FUNC)
|
14
|
+
// Put global functions here.
|
15
|
+
// Include in your views with
|
16
|
+
//
|
17
|
+
// //include-lib
|
18
|
+
FUNC
|
19
|
+
|
20
|
+
design_names.each do |design_name|
|
21
|
+
subdirectory = File.join(directory, design_name)
|
22
|
+
FileUtils.mkdir_p(subdirectory)
|
23
|
+
filename = File.join(subdirectory, "sample-map.js")
|
24
|
+
self.write(filename, <<-FUNC)
|
25
|
+
function(doc) {
|
26
|
+
// Keys is first letter of _id
|
27
|
+
emit(doc._id[0], doc);
|
28
|
+
}
|
29
|
+
FUNC
|
30
|
+
|
31
|
+
filename = File.join(subdirectory, "sample-reduce.js")
|
32
|
+
self.write(filename, <<-FUNC)
|
33
|
+
function(keys, values) {
|
34
|
+
// Count the number of keys starting with this letter
|
35
|
+
return values.length;
|
36
|
+
}
|
37
|
+
FUNC
|
38
|
+
|
39
|
+
filename = File.join(subdirectory, "lib.js")
|
40
|
+
self.write(filename, <<-FUNC)
|
41
|
+
// Put functions specific to '#{design_name}' here.
|
42
|
+
// Include in your views with
|
43
|
+
//
|
44
|
+
// //include-lib
|
45
|
+
FUNC
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.help
|
50
|
+
helpstring = <<-GEN
|
51
|
+
|
52
|
+
Usage: couchview generate directory design1 design2 design3 ...
|
53
|
+
|
54
|
+
Couchview will create directories and example views for the design documents you specify.
|
55
|
+
|
56
|
+
GEN
|
57
|
+
helpstring.gsub(/^ /, '')
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.write(filename, contents)
|
61
|
+
puts "Writing #{filename}"
|
62
|
+
File.open(filename, "w") do |f|
|
63
|
+
# Remove leading spaces
|
64
|
+
contents.gsub!(/^ ( )?/, '')
|
65
|
+
f.write contents
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
module CouchRest
|
2
|
+
|
3
|
+
module Commands
|
4
|
+
|
5
|
+
module Push
|
6
|
+
|
7
|
+
def self.run(options)
|
8
|
+
directory = options[:directory]
|
9
|
+
database = options[:trailing_args].first
|
10
|
+
|
11
|
+
fm = CouchRest::FileManager.new(database)
|
12
|
+
fm.loud = options[:loud]
|
13
|
+
|
14
|
+
if options[:loud]
|
15
|
+
puts "Pushing views from directory #{directory} to database #{fm.db}"
|
16
|
+
end
|
17
|
+
|
18
|
+
fm.push_views(directory)
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.help
|
22
|
+
helpstring = <<-GEN
|
23
|
+
|
24
|
+
== Pushing views with Couchview ==
|
25
|
+
|
26
|
+
Usage: couchview push directory dbname
|
27
|
+
|
28
|
+
Couchview expects a specific filesystem layout for your CouchDB views (see
|
29
|
+
example below). It also supports advanced features like inlining of library
|
30
|
+
code (so you can keep DRY) as well as avoiding unnecessary document
|
31
|
+
modification.
|
32
|
+
|
33
|
+
Couchview also solves a problem with CouchDB's view API, which only provides
|
34
|
+
access to the final reduce side of any views which have both a map and a
|
35
|
+
reduce function defined. The intermediate map results are often useful for
|
36
|
+
development and production. CouchDB is smart enough to reuse map indexes for
|
37
|
+
functions duplicated across views within the same design document.
|
38
|
+
|
39
|
+
For views with a reduce function defined, Couchview creates both a reduce view
|
40
|
+
and a map-only view, so that you can browse and query the map side as well as
|
41
|
+
the reduction, with no performance penalty.
|
42
|
+
|
43
|
+
== Example ==
|
44
|
+
|
45
|
+
couchview push foo-project/bar-views baz-database
|
46
|
+
|
47
|
+
This will push the views defined in foo-project/bar-views into a database
|
48
|
+
called baz-database. Couchview expects the views to be defined in files with
|
49
|
+
names like:
|
50
|
+
|
51
|
+
foo-project/bar-views/my-design/viewname-map.js
|
52
|
+
foo-project/bar-views/my-design/viewname-reduce.js
|
53
|
+
foo-project/bar-views/my-design/noreduce-map.js
|
54
|
+
|
55
|
+
Pushed to => http://127.0.0.1:5984/baz-database/_design/my-design
|
56
|
+
|
57
|
+
And the design document:
|
58
|
+
{
|
59
|
+
"views" : {
|
60
|
+
"viewname-map" : {
|
61
|
+
"map" : "### contents of view-name-map.js ###"
|
62
|
+
},
|
63
|
+
"viewname-reduce" : {
|
64
|
+
"map" : "### contents of view-name-map.js ###",
|
65
|
+
"reduce" : "### contents of view-name-reduce.js ###"
|
66
|
+
},
|
67
|
+
"noreduce-map" : {
|
68
|
+
"map" : "### contents of noreduce-map.js ###"
|
69
|
+
}
|
70
|
+
}
|
71
|
+
}
|
72
|
+
|
73
|
+
Couchview will create a design document for each subdirectory of the views
|
74
|
+
directory specified on the command line.
|
75
|
+
|
76
|
+
== Library Inlining ==
|
77
|
+
|
78
|
+
Couchview can optionally inline library code into your views so you only have
|
79
|
+
to maintain it in one place. It looks for any files named lib.* in your
|
80
|
+
design-doc directory (for doc specific libs) and in the parent views directory
|
81
|
+
(for project global libs). These libraries are only inserted into views which
|
82
|
+
include the text
|
83
|
+
|
84
|
+
// !include lib
|
85
|
+
|
86
|
+
or
|
87
|
+
|
88
|
+
# !include lib
|
89
|
+
|
90
|
+
Couchview is a result of scratching my own itch. I'd be happy to make it more
|
91
|
+
general, so please contact me at jchris@grabb.it if you'd like to see anything
|
92
|
+
added or changed.
|
93
|
+
|
94
|
+
GEN
|
95
|
+
helpstring.gsub(/^ /, '')
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
@@ -0,0 +1,239 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
require "base64"
|
3
|
+
|
4
|
+
module CouchRest
|
5
|
+
class Database
|
6
|
+
attr_reader :server, :host, :name, :root
|
7
|
+
attr_accessor :bulk_save_cache_limit
|
8
|
+
|
9
|
+
# Create a CouchRest::Database adapter for the supplied CouchRest::Server
|
10
|
+
# and database name.
|
11
|
+
#
|
12
|
+
# ==== Parameters
|
13
|
+
# server<CouchRest::Server>:: database host
|
14
|
+
# name<String>:: database name
|
15
|
+
#
|
16
|
+
def initialize server, name
|
17
|
+
@name = name
|
18
|
+
@server = server
|
19
|
+
@host = server.uri
|
20
|
+
@root = "#{host}/#{name}"
|
21
|
+
@streamer = Streamer.new(self)
|
22
|
+
@bulk_save_cache = []
|
23
|
+
@bulk_save_cache_limit = 50
|
24
|
+
end
|
25
|
+
|
26
|
+
# returns the database's uri
|
27
|
+
def to_s
|
28
|
+
@root
|
29
|
+
end
|
30
|
+
|
31
|
+
# GET the database info from CouchDB
|
32
|
+
def info
|
33
|
+
CouchRest.get @root
|
34
|
+
end
|
35
|
+
|
36
|
+
# Query the <tt>_all_docs</tt> view. Accepts all the same arguments as view.
|
37
|
+
def documents params = {}
|
38
|
+
keys = params.delete(:keys)
|
39
|
+
url = CouchRest.paramify_url "#{@root}/_all_docs", params
|
40
|
+
if keys
|
41
|
+
CouchRest.post(url, {:keys => keys})
|
42
|
+
else
|
43
|
+
CouchRest.get url
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# POST a temporary view function to CouchDB for querying. This is not
|
48
|
+
# recommended, as you don't get any performance benefit from CouchDB's
|
49
|
+
# materialized views. Can be quite slow on large databases.
|
50
|
+
def slow_view funcs, params = {}
|
51
|
+
keys = params.delete(:keys)
|
52
|
+
funcs = funcs.merge({:keys => keys}) if keys
|
53
|
+
url = CouchRest.paramify_url "#{@root}/_slow_view", params
|
54
|
+
JSON.parse(RestClient.post(url, funcs.to_json, {"Content-Type" => 'application/json'}))
|
55
|
+
end
|
56
|
+
|
57
|
+
# backwards compatibility is a plus
|
58
|
+
alias :temp_view :slow_view
|
59
|
+
|
60
|
+
# Query a CouchDB view as defined by a <tt>_design</tt> document. Accepts
|
61
|
+
# paramaters as described in http://wiki.apache.org/couchdb/HttpViewApi
|
62
|
+
def view name, params = {}, &block
|
63
|
+
keys = params.delete(:keys)
|
64
|
+
url = CouchRest.paramify_url "#{@root}/_view/#{name}", params
|
65
|
+
if keys
|
66
|
+
CouchRest.post(url, {:keys => keys})
|
67
|
+
else
|
68
|
+
if block_given?
|
69
|
+
@streamer.view(name, params, &block)
|
70
|
+
else
|
71
|
+
CouchRest.get url
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# GET a document from CouchDB, by id. Returns a Ruby Hash.
|
77
|
+
def get id
|
78
|
+
slug = escape_docid(id)
|
79
|
+
hash = CouchRest.get("#{@root}/#{slug}")
|
80
|
+
doc = if /^_design/ =~ hash["_id"]
|
81
|
+
Design.new(hash)
|
82
|
+
else
|
83
|
+
Document.new(hash)
|
84
|
+
end
|
85
|
+
doc.database = self
|
86
|
+
doc
|
87
|
+
end
|
88
|
+
|
89
|
+
# GET an attachment directly from CouchDB
|
90
|
+
def fetch_attachment docid, name
|
91
|
+
slug = escape_docid(docid)
|
92
|
+
name = CGI.escape(name)
|
93
|
+
RestClient.get "#{@root}/#{slug}/#{name}"
|
94
|
+
end
|
95
|
+
|
96
|
+
# PUT an attachment directly to CouchDB
|
97
|
+
def put_attachment doc, name, file, options = {}
|
98
|
+
docid = escape_docid(doc['_id'])
|
99
|
+
name = CGI.escape(name)
|
100
|
+
uri = if doc['_rev']
|
101
|
+
"#{@root}/#{docid}/#{name}?rev=#{doc['_rev']}"
|
102
|
+
else
|
103
|
+
"#{@root}/#{docid}/#{name}"
|
104
|
+
end
|
105
|
+
|
106
|
+
JSON.parse(RestClient.put(uri, file, options))
|
107
|
+
end
|
108
|
+
|
109
|
+
# Save a document to CouchDB. This will use the <tt>_id</tt> field from
|
110
|
+
# the document as the id for PUT, or request a new UUID from CouchDB, if
|
111
|
+
# no <tt>_id</tt> is present on the document. IDs are attached to
|
112
|
+
# documents on the client side because POST has the curious property of
|
113
|
+
# being automatically retried by proxies in the event of network
|
114
|
+
# segmentation and lost responses.
|
115
|
+
#
|
116
|
+
# If <tt>bulk</tt> is true (false by default) the document is cached for bulk-saving later.
|
117
|
+
# Bulk saving happens automatically when #bulk_save_cache limit is exceded, or on the next non bulk save.
|
118
|
+
def save (doc, bulk = false)
|
119
|
+
if doc['_attachments']
|
120
|
+
doc['_attachments'] = encode_attachments(doc['_attachments'])
|
121
|
+
end
|
122
|
+
if bulk
|
123
|
+
@bulk_save_cache << doc
|
124
|
+
return bulk_save if @bulk_save_cache.length >= @bulk_save_cache_limit
|
125
|
+
return {"ok" => true} # Compatibility with Document#save
|
126
|
+
elsif !bulk && @bulk_save_cache.length > 0
|
127
|
+
bulk_save
|
128
|
+
end
|
129
|
+
result = if doc['_id']
|
130
|
+
slug = escape_docid(doc['_id'])
|
131
|
+
CouchRest.put "#{@root}/#{slug}", doc
|
132
|
+
else
|
133
|
+
begin
|
134
|
+
slug = doc['_id'] = @server.next_uuid
|
135
|
+
CouchRest.put "#{@root}/#{slug}", doc
|
136
|
+
rescue #old version of couchdb
|
137
|
+
CouchRest.post @root, doc
|
138
|
+
end
|
139
|
+
end
|
140
|
+
if result['ok']
|
141
|
+
doc['_id'] = result['id']
|
142
|
+
doc['_rev'] = result['rev']
|
143
|
+
doc.database = self if doc.respond_to?(:database=)
|
144
|
+
end
|
145
|
+
result
|
146
|
+
end
|
147
|
+
|
148
|
+
# POST an array of documents to CouchDB. If any of the documents are
|
149
|
+
# missing ids, supply one from the uuid cache.
|
150
|
+
#
|
151
|
+
# If called with no arguments, bulk saves the cache of documents to be bulk saved.
|
152
|
+
def bulk_save (docs = nil)
|
153
|
+
if docs.nil?
|
154
|
+
docs = @bulk_save_cache
|
155
|
+
@bulk_save_cache = []
|
156
|
+
end
|
157
|
+
ids, noids = docs.partition{|d|d['_id']}
|
158
|
+
uuid_count = [noids.length, @server.uuid_batch_count].max
|
159
|
+
noids.each do |doc|
|
160
|
+
nextid = @server.next_uuid(uuid_count) rescue nil
|
161
|
+
doc['_id'] = nextid if nextid
|
162
|
+
end
|
163
|
+
CouchRest.post "#{@root}/_bulk_docs", {:docs => docs}
|
164
|
+
end
|
165
|
+
|
166
|
+
# DELETE the document from CouchDB that has the given <tt>_id</tt> and
|
167
|
+
# <tt>_rev</tt>.
|
168
|
+
#
|
169
|
+
# If <tt>bulk</tt> is true (false by default) the deletion is recorded for bulk-saving (bulk-deletion :) later.
|
170
|
+
# Bulk saving happens automatically when #bulk_save_cache limit is exceded, or on the next non bulk save.
|
171
|
+
def delete (doc, bulk = false)
|
172
|
+
raise ArgumentError, "_id and _rev required for deleting" unless doc['_id'] && doc['_rev']
|
173
|
+
if bulk
|
174
|
+
@bulk_save_cache << { '_id' => doc['_id'], '_rev' => doc['_rev'], '_deleted' => true }
|
175
|
+
return bulk_save if @bulk_save_cache.length >= @bulk_save_cache_limit
|
176
|
+
return { "ok" => true } # Mimic the non-deferred version
|
177
|
+
end
|
178
|
+
slug = escape_docid(doc['_id'])
|
179
|
+
CouchRest.delete "#{@root}/#{slug}?rev=#{doc['_rev']}"
|
180
|
+
end
|
181
|
+
|
182
|
+
# COPY an existing document to a new id. If the destination id currently exists, a rev must be provided.
|
183
|
+
# <tt>dest</tt> can take one of two forms if overwriting: "id_to_overwrite?rev=revision" or the actual doc
|
184
|
+
# hash with a '_rev' key
|
185
|
+
def copy doc, dest
|
186
|
+
raise ArgumentError, "_id is required for copying" unless doc['_id']
|
187
|
+
slug = escape_docid(doc['_id'])
|
188
|
+
destination = if dest.respond_to?(:has_key?) && dest['_id'] && dest['_rev']
|
189
|
+
"#{dest['_id']}?rev=#{dest['_rev']}"
|
190
|
+
else
|
191
|
+
dest
|
192
|
+
end
|
193
|
+
CouchRest.copy "#{@root}/#{slug}", destination
|
194
|
+
end
|
195
|
+
|
196
|
+
# MOVE an existing document to a new id. If the destination id currently exists, a rev must be provided.
|
197
|
+
# <tt>dest</tt> can take one of two forms if overwriting: "id_to_overwrite?rev=revision" or the actual doc
|
198
|
+
# hash with a '_rev' key
|
199
|
+
def move doc, dest
|
200
|
+
raise ArgumentError, "_id and _rev are required for moving" unless doc['_id'] && doc['_rev']
|
201
|
+
slug = escape_docid(doc['_id'])
|
202
|
+
destination = if dest.respond_to?(:has_key?) && dest['_id'] && dest['_rev']
|
203
|
+
"#{dest['_id']}?rev=#{dest['_rev']}"
|
204
|
+
else
|
205
|
+
dest
|
206
|
+
end
|
207
|
+
CouchRest.move "#{@root}/#{slug}?rev=#{doc['_rev']}", destination
|
208
|
+
end
|
209
|
+
|
210
|
+
# Compact the database, removing old document revisions and optimizing space use.
|
211
|
+
def compact!
|
212
|
+
CouchRest.post "#{@root}/_compact"
|
213
|
+
end
|
214
|
+
|
215
|
+
# DELETE the database itself. This is not undoable and could be rather
|
216
|
+
# catastrophic. Use with care!
|
217
|
+
def delete!
|
218
|
+
CouchRest.delete @root
|
219
|
+
end
|
220
|
+
|
221
|
+
private
|
222
|
+
|
223
|
+
def escape_docid id
|
224
|
+
/^_design\/(.*)/ =~ id ? "_design/#{CGI.escape($1)}" : CGI.escape(id)
|
225
|
+
end
|
226
|
+
|
227
|
+
def encode_attachments attachments
|
228
|
+
attachments.each do |k,v|
|
229
|
+
next if v['stub']
|
230
|
+
v['data'] = base64(v['data'])
|
231
|
+
end
|
232
|
+
attachments
|
233
|
+
end
|
234
|
+
|
235
|
+
def base64 data
|
236
|
+
Base64.encode64(data).gsub(/\s/,'')
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module CouchRest
|
2
|
+
class Design < Document
|
3
|
+
def view_by *keys
|
4
|
+
opts = keys.pop if keys.last.is_a?(Hash)
|
5
|
+
opts ||= {}
|
6
|
+
self['views'] ||= {}
|
7
|
+
method_name = "by_#{keys.join('_and_')}"
|
8
|
+
|
9
|
+
if opts[:map]
|
10
|
+
view = {}
|
11
|
+
view['map'] = opts.delete(:map)
|
12
|
+
if opts[:reduce]
|
13
|
+
view['reduce'] = opts.delete(:reduce)
|
14
|
+
opts[:reduce] = false
|
15
|
+
end
|
16
|
+
self['views'][method_name] = view
|
17
|
+
else
|
18
|
+
doc_keys = keys.collect{|k|"doc['#{k}']"} # this is where :require => 'doc.x == true' would show up
|
19
|
+
key_emit = doc_keys.length == 1 ? "#{doc_keys.first}" : "[#{doc_keys.join(', ')}]"
|
20
|
+
guards = opts.delete(:guards) || []
|
21
|
+
guards.concat doc_keys
|
22
|
+
map_function = <<-JAVASCRIPT
|
23
|
+
function(doc) {
|
24
|
+
if (#{guards.join(' && ')}) {
|
25
|
+
emit(#{key_emit}, null);
|
26
|
+
}
|
27
|
+
}
|
28
|
+
JAVASCRIPT
|
29
|
+
self['views'][method_name] = {
|
30
|
+
'map' => map_function
|
31
|
+
}
|
32
|
+
end
|
33
|
+
self['views'][method_name]['couchrest-defaults'] = opts unless opts.empty?
|
34
|
+
method_name
|
35
|
+
end
|
36
|
+
|
37
|
+
# Dispatches to any named view.
|
38
|
+
def view view_name, query={}, &block
|
39
|
+
view_name = view_name.to_s
|
40
|
+
view_slug = "#{name}/#{view_name}"
|
41
|
+
defaults = (self['views'][view_name] && self['views'][view_name]["couchrest-defaults"]) || {}
|
42
|
+
fetch_view(view_slug, defaults.merge(query), &block)
|
43
|
+
end
|
44
|
+
|
45
|
+
def name
|
46
|
+
id.sub('_design/','') if id
|
47
|
+
end
|
48
|
+
|
49
|
+
def name= newname
|
50
|
+
self['_id'] = "_design/#{newname}"
|
51
|
+
end
|
52
|
+
|
53
|
+
def save
|
54
|
+
raise ArgumentError, "_design docs require a name" unless name && name.length > 0
|
55
|
+
super
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
# returns stored defaults if the there is a view named this in the design doc
|
61
|
+
def has_view?(view)
|
62
|
+
view = view.to_s
|
63
|
+
self['views'][view] &&
|
64
|
+
(self['views'][view]["couchrest-defaults"]||{})
|
65
|
+
end
|
66
|
+
|
67
|
+
# def fetch_view_with_docs name, opts, raw=false, &block
|
68
|
+
# if raw
|
69
|
+
# fetch_view name, opts, &block
|
70
|
+
# else
|
71
|
+
# begin
|
72
|
+
# view = fetch_view name, opts.merge({:include_docs => true}), &block
|
73
|
+
# view['rows'].collect{|r|new(r['doc'])} if view['rows']
|
74
|
+
# rescue
|
75
|
+
# # fallback for old versions of couchdb that don't
|
76
|
+
# # have include_docs support
|
77
|
+
# view = fetch_view name, opts, &block
|
78
|
+
# view['rows'].collect{|r|new(database.get(r['id']))} if view['rows']
|
79
|
+
# end
|
80
|
+
# end
|
81
|
+
# end
|
82
|
+
|
83
|
+
def fetch_view view_name, opts, &block
|
84
|
+
database.view(view_name, opts, &block)
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module CouchRest
|
2
|
+
class Response < Hash
|
3
|
+
def initialize keys = {}
|
4
|
+
keys.each do |k,v|
|
5
|
+
self[k.to_s] = v
|
6
|
+
end
|
7
|
+
end
|
8
|
+
def []= key, value
|
9
|
+
super(key.to_s, value)
|
10
|
+
end
|
11
|
+
def [] key
|
12
|
+
super(key.to_s)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Document < Response
|
17
|
+
|
18
|
+
attr_accessor :database
|
19
|
+
|
20
|
+
# alias for self['_id']
|
21
|
+
def id
|
22
|
+
self['_id']
|
23
|
+
end
|
24
|
+
|
25
|
+
# alias for self['_rev']
|
26
|
+
def rev
|
27
|
+
self['_rev']
|
28
|
+
end
|
29
|
+
|
30
|
+
# returns true if the document has never been saved
|
31
|
+
def new_document?
|
32
|
+
!rev
|
33
|
+
end
|
34
|
+
|
35
|
+
# Saves the document to the db using create or update. Also runs the :save
|
36
|
+
# callbacks. Sets the <tt>_id</tt> and <tt>_rev</tt> fields based on
|
37
|
+
# CouchDB's response.
|
38
|
+
# If <tt>bulk</tt> is <tt>true</tt> (defaults to false) the document is cached for bulk save.
|
39
|
+
def save(bulk = false)
|
40
|
+
raise ArgumentError, "doc.database required for saving" unless database
|
41
|
+
result = database.save self, bulk
|
42
|
+
result['ok']
|
43
|
+
end
|
44
|
+
|
45
|
+
# Deletes the document from the database. Runs the :delete callbacks.
|
46
|
+
# Removes the <tt>_id</tt> and <tt>_rev</tt> fields, preparing the
|
47
|
+
# document to be saved to a new <tt>_id</tt>.
|
48
|
+
# If <tt>bulk</tt> is <tt>true</tt> (defaults to false) the document won't
|
49
|
+
# actually be deleted from the db until bulk save.
|
50
|
+
def destroy(bulk = false)
|
51
|
+
raise ArgumentError, "doc.database required to destroy" unless database
|
52
|
+
result = database.delete(self, bulk)
|
53
|
+
if result['ok']
|
54
|
+
self['_rev'] = nil
|
55
|
+
self['_id'] = nil
|
56
|
+
end
|
57
|
+
result['ok']
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
end
|