juozasg-couchrest 0.10.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +176 -0
- data/README.rdoc +67 -0
- data/Rakefile +86 -0
- data/THANKS +15 -0
- data/bin/couchapp +58 -0
- data/bin/couchdir +20 -0
- data/bin/couchview +48 -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 +173 -0
- data/lib/couchrest/core/design.rb +89 -0
- data/lib/couchrest/core/document.rb +60 -0
- data/lib/couchrest/core/model.rb +557 -0
- data/lib/couchrest/core/server.rb +51 -0
- data/lib/couchrest/core/view.rb +4 -0
- data/lib/couchrest/helper/file_manager.rb +317 -0
- data/lib/couchrest/helper/pager.rb +103 -0
- data/lib/couchrest/helper/streamer.rb +44 -0
- data/lib/couchrest/helper/templates/bar.txt +11 -0
- data/lib/couchrest/helper/templates/example-map.js +8 -0
- data/lib/couchrest/helper/templates/example-reduce.js +10 -0
- data/lib/couchrest/helper/templates/index.html +26 -0
- data/lib/couchrest/monkeypatches.rb +24 -0
- data/lib/couchrest.rb +125 -0
- data/spec/couchapp_spec.rb +87 -0
- data/spec/couchrest/core/couchrest_spec.rb +191 -0
- data/spec/couchrest/core/database_spec.rb +478 -0
- data/spec/couchrest/core/design_spec.rb +131 -0
- data/spec/couchrest/core/document_spec.rb +96 -0
- data/spec/couchrest/core/model_spec.rb +660 -0
- data/spec/couchrest/helpers/file_manager_spec.rb +203 -0
- data/spec/couchrest/helpers/pager_spec.rb +122 -0
- data/spec/couchrest/helpers/streamer_spec.rb +23 -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 +14 -0
- data/utils/remap.rb +27 -0
- data/utils/subset.rb +30 -0
- metadata +154 -0
@@ -0,0 +1,317 @@
|
|
1
|
+
require 'digest/md5'
|
2
|
+
|
3
|
+
module CouchRest
|
4
|
+
class FileManager
|
5
|
+
attr_reader :db
|
6
|
+
attr_accessor :loud
|
7
|
+
|
8
|
+
LANGS = {"rb" => "ruby", "js" => "javascript"}
|
9
|
+
MIMES = {
|
10
|
+
"html" => "text/html",
|
11
|
+
"htm" => "text/html",
|
12
|
+
"png" => "image/png",
|
13
|
+
"gif" => "image/gif",
|
14
|
+
"css" => "text/css",
|
15
|
+
"js" => "test/javascript"
|
16
|
+
}
|
17
|
+
def initialize(dbname, host="http://localhost:5984")
|
18
|
+
@db = CouchRest.new(host).database(dbname)
|
19
|
+
end
|
20
|
+
|
21
|
+
def push_directory(push_dir, docid=nil)
|
22
|
+
docid ||= push_dir.split('/').reverse.find{|part|!part.empty?}
|
23
|
+
|
24
|
+
pushfiles = Dir["#{push_dir}/**/*.*"].collect do |f|
|
25
|
+
{f.split("#{push_dir}/").last => open(f).read}
|
26
|
+
end
|
27
|
+
|
28
|
+
return if pushfiles.empty?
|
29
|
+
|
30
|
+
@attachments = {}
|
31
|
+
@signatures = {}
|
32
|
+
pushfiles.each do |file|
|
33
|
+
name = file.keys.first
|
34
|
+
value = file.values.first
|
35
|
+
@signatures[name] = md5(value)
|
36
|
+
|
37
|
+
@attachments[name] = {
|
38
|
+
"data" => value,
|
39
|
+
"content_type" => MIMES[name.split('.').last]
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
doc = @db.get(docid) rescue nil
|
44
|
+
|
45
|
+
unless doc
|
46
|
+
say "creating #{docid}"
|
47
|
+
@db.save({"_id" => docid, "_attachments" => @attachments, "signatures" => @signatures})
|
48
|
+
return
|
49
|
+
end
|
50
|
+
|
51
|
+
doc["signatures"] ||= {}
|
52
|
+
doc["_attachments"] ||= {}
|
53
|
+
# remove deleted docs
|
54
|
+
to_be_removed = doc["signatures"].keys.select do |d|
|
55
|
+
!pushfiles.collect{|p| p.keys.first}.include?(d)
|
56
|
+
end
|
57
|
+
|
58
|
+
to_be_removed.each do |p|
|
59
|
+
say "deleting #{p}"
|
60
|
+
doc["signatures"].delete(p)
|
61
|
+
doc["_attachments"].delete(p)
|
62
|
+
end
|
63
|
+
|
64
|
+
# update existing docs:
|
65
|
+
doc["signatures"].each do |path, sig|
|
66
|
+
if (@signatures[path] == sig)
|
67
|
+
say "no change to #{path}. skipping..."
|
68
|
+
else
|
69
|
+
say "replacing #{path}"
|
70
|
+
doc["signatures"][path] = md5(@attachments[path]["data"])
|
71
|
+
doc["_attachments"][path].delete("stub")
|
72
|
+
doc["_attachments"][path].delete("length")
|
73
|
+
doc["_attachments"][path]["data"] = @attachments[path]["data"]
|
74
|
+
doc["_attachments"][path].merge!({"data" => @attachments[path]["data"]} )
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# add in new files
|
79
|
+
new_files = pushfiles.select{|d| !doc["signatures"].keys.include?( d.keys.first) }
|
80
|
+
|
81
|
+
new_files.each do |f|
|
82
|
+
say "creating #{f}"
|
83
|
+
path = f.keys.first
|
84
|
+
content = f.values.first
|
85
|
+
doc["signatures"][path] = md5(content)
|
86
|
+
|
87
|
+
doc["_attachments"][path] = {
|
88
|
+
"data" => content,
|
89
|
+
"content_type" => MIMES[path.split('.').last]
|
90
|
+
}
|
91
|
+
end
|
92
|
+
|
93
|
+
begin
|
94
|
+
@db.save(doc)
|
95
|
+
rescue Exception => e
|
96
|
+
say e.message
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def push_views(view_dir)
|
101
|
+
designs = {}
|
102
|
+
|
103
|
+
Dir["#{view_dir}/**/*.*"].each do |design_doc|
|
104
|
+
design_doc_parts = design_doc.split('/')
|
105
|
+
next if /^lib\..*$/.match design_doc_parts.last
|
106
|
+
pre_normalized_view_name = design_doc_parts.last.split("-")
|
107
|
+
view_name = pre_normalized_view_name[0..pre_normalized_view_name.length-2].join("-")
|
108
|
+
|
109
|
+
folder = design_doc_parts[-2]
|
110
|
+
|
111
|
+
designs[folder] ||= {}
|
112
|
+
designs[folder]["views"] ||= {}
|
113
|
+
design_lang = design_doc_parts.last.split(".").last
|
114
|
+
designs[folder]["language"] ||= LANGS[design_lang]
|
115
|
+
|
116
|
+
libs = ""
|
117
|
+
Dir["#{view_dir}/lib.#{design_lang}"].collect do |global_lib|
|
118
|
+
libs << open(global_lib).read
|
119
|
+
libs << "\n"
|
120
|
+
end
|
121
|
+
Dir["#{view_dir}/#{folder}/lib.#{design_lang}"].collect do |global_lib|
|
122
|
+
libs << open(global_lib).read
|
123
|
+
libs << "\n"
|
124
|
+
end
|
125
|
+
if design_doc_parts.last =~ /-map/
|
126
|
+
designs[folder]["views"]["#{view_name}-map"] ||= {}
|
127
|
+
|
128
|
+
designs[folder]["views"]["#{view_name}-map"]["map"] = read(design_doc, libs)
|
129
|
+
|
130
|
+
designs[folder]["views"]["#{view_name}-reduce"] ||= {}
|
131
|
+
designs[folder]["views"]["#{view_name}-reduce"]["map"] = read(design_doc, libs)
|
132
|
+
end
|
133
|
+
|
134
|
+
if design_doc_parts.last =~ /-reduce/
|
135
|
+
designs[folder]["views"]["#{view_name}-reduce"] ||= {}
|
136
|
+
|
137
|
+
designs[folder]["views"]["#{view_name}-reduce"]["reduce"] = read(design_doc, libs)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# cleanup empty maps and reduces
|
142
|
+
designs.each do |name, props|
|
143
|
+
props["views"].each do |view, funcs|
|
144
|
+
next unless view.include?("reduce")
|
145
|
+
props["views"].delete(view) unless funcs.keys.include?("reduce")
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
designs.each do |k,v|
|
150
|
+
create_or_update("_design/#{k}", v)
|
151
|
+
end
|
152
|
+
|
153
|
+
designs
|
154
|
+
end
|
155
|
+
|
156
|
+
def pull_views(view_dir)
|
157
|
+
prefix = "_design"
|
158
|
+
ds = db.documents(:startkey => '#{prefix}/', :endkey => '#{prefix}/ZZZZZZZZZ')
|
159
|
+
ds['rows'].collect{|r|r['id']}.each do |id|
|
160
|
+
puts directory = id.split('/').last
|
161
|
+
FileUtils.mkdir_p(File.join(view_dir,directory))
|
162
|
+
views = db.get(id)['views']
|
163
|
+
|
164
|
+
vgroups = views.keys.group_by{|k|k.sub(/\-(map|reduce)$/,'')}
|
165
|
+
vgroups.each do|g,vs|
|
166
|
+
mapname = vs.find {|v|views[v]["map"]}
|
167
|
+
if mapname
|
168
|
+
# save map
|
169
|
+
mapfunc = views[mapname]["map"]
|
170
|
+
mapfile = File.join(view_dir, directory, "#{g}-map.js") # todo support non-js views
|
171
|
+
File.open(mapfile,'w') do |f|
|
172
|
+
f.write mapfunc
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
reducename = vs.find {|v|views[v]["reduce"]}
|
177
|
+
if reducename
|
178
|
+
# save reduce
|
179
|
+
reducefunc = views[reducename]["reduce"]
|
180
|
+
reducefile = File.join(view_dir, directory, "#{g}-reduce.js") # todo support non-js views
|
181
|
+
File.open(reducefile,'w') do |f|
|
182
|
+
f.write reducefunc
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
end
|
189
|
+
|
190
|
+
def push_app(appdir, appname)
|
191
|
+
libs = []
|
192
|
+
viewdir = File.join(appdir,"views")
|
193
|
+
attachdir = File.join(appdir,"_attachments")
|
194
|
+
views, lang = read_design_views(viewdir)
|
195
|
+
|
196
|
+
docid = "_design/#{appname}"
|
197
|
+
design = @db.get(docid) rescue {}
|
198
|
+
design['_id'] = docid
|
199
|
+
design['views'] = views
|
200
|
+
design['language'] = lang if lang
|
201
|
+
@db.save(design)
|
202
|
+
push_directory(attachdir, docid)
|
203
|
+
|
204
|
+
push_fields(appdir, docid)
|
205
|
+
end
|
206
|
+
|
207
|
+
def push_fields(appdir, docid)
|
208
|
+
fields = {}
|
209
|
+
(Dir["#{appdir}/**/*.*"] -
|
210
|
+
Dir["#{appdir}/views/**/*.*"] -
|
211
|
+
Dir["#{appdir}/doc.json"] -
|
212
|
+
Dir["#{appdir}/_attachments/**/*.*"]).each do |file|
|
213
|
+
farray = file.sub(appdir, '').sub(/^\//,'').split('/')
|
214
|
+
myfield = fields
|
215
|
+
while farray.length > 1
|
216
|
+
front = farray.shift
|
217
|
+
myfield[front] ||= {}
|
218
|
+
myfield = myfield[front]
|
219
|
+
end
|
220
|
+
fname, fext = farray.shift.split('.')
|
221
|
+
fguts = File.open(file).read
|
222
|
+
if fext == 'json'
|
223
|
+
myfield[fname] = JSON.parse(fguts)
|
224
|
+
else
|
225
|
+
myfield[fname] = fguts
|
226
|
+
end
|
227
|
+
end
|
228
|
+
if File.exists?("#{appdir}/doc.json")
|
229
|
+
default_json = JSON.parse(File.open("#{appdir}/doc.json").read)
|
230
|
+
end
|
231
|
+
design = @db.get(docid) rescue {}
|
232
|
+
design.merge!(fields)
|
233
|
+
design.merge!(default_json) if default_json
|
234
|
+
@db.save(design)
|
235
|
+
end
|
236
|
+
|
237
|
+
# Generate an application in the given directory.
|
238
|
+
# This is a class method because it doesn't depend on
|
239
|
+
# specifying a database.
|
240
|
+
def self.generate_app(app_dir)
|
241
|
+
FileUtils.mkdir_p(app_dir)
|
242
|
+
FileUtils.mkdir_p(File.join(app_dir,"_attachments"))
|
243
|
+
FileUtils.mkdir_p(File.join(app_dir,"views"))
|
244
|
+
FileUtils.mkdir_p(File.join(app_dir,"foo"))
|
245
|
+
|
246
|
+
{
|
247
|
+
"index.html" => "_attachments",
|
248
|
+
'example-map.js' => "views",
|
249
|
+
'example-reduce.js' => "views",
|
250
|
+
'bar.txt' => "foo",
|
251
|
+
}.each do |filename, targetdir|
|
252
|
+
template = File.join(File.expand_path(File.dirname(__FILE__)), 'templates',filename)
|
253
|
+
dest = File.join(app_dir,targetdir,filename)
|
254
|
+
FileUtils.cp(template, dest)
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
private
|
259
|
+
|
260
|
+
def read_design_views(viewdir)
|
261
|
+
libs = []
|
262
|
+
language = nil
|
263
|
+
views = {}
|
264
|
+
Dir["#{viewdir}/*.*"].each do |viewfile|
|
265
|
+
view_parts = viewfile.split('/')
|
266
|
+
viewfile_name = view_parts.last
|
267
|
+
# example-map.js
|
268
|
+
viewfile_name_parts = viewfile_name.split('.')
|
269
|
+
viewfile_ext = viewfile_name_parts.last
|
270
|
+
view_name_parts = viewfile_name_parts.first.split('-')
|
271
|
+
func_type = view_name_parts.pop
|
272
|
+
view_name = view_name_parts.join('-')
|
273
|
+
contents = File.open(viewfile).read
|
274
|
+
if /^lib\..*$/.match viewfile_name
|
275
|
+
libs.push(contents)
|
276
|
+
else
|
277
|
+
views[view_name] ||= {}
|
278
|
+
language = LANGS[viewfile_ext]
|
279
|
+
views[view_name][func_type] = contents.sub(/(\/\/|#)include-lib/,libs.join("\n"))
|
280
|
+
end
|
281
|
+
end
|
282
|
+
[views, language]
|
283
|
+
end
|
284
|
+
|
285
|
+
def say words
|
286
|
+
puts words if @loud
|
287
|
+
end
|
288
|
+
|
289
|
+
def md5 string
|
290
|
+
Digest::MD5.hexdigest(string)
|
291
|
+
end
|
292
|
+
|
293
|
+
def read(file, libs=nil)
|
294
|
+
st = open(file).read
|
295
|
+
st.sub!(/(\/\/|#)include-lib/,libs) if libs
|
296
|
+
st
|
297
|
+
end
|
298
|
+
|
299
|
+
def create_or_update(id, fields)
|
300
|
+
existing = @db.get(id) rescue nil
|
301
|
+
|
302
|
+
if existing
|
303
|
+
updated = existing.merge(fields)
|
304
|
+
if existing != updated
|
305
|
+
say "replacing #{id}"
|
306
|
+
db.save(updated)
|
307
|
+
else
|
308
|
+
say "skipping #{id}"
|
309
|
+
end
|
310
|
+
else
|
311
|
+
say "creating #{id}"
|
312
|
+
db.save(fields.merge({"_id" => id}))
|
313
|
+
end
|
314
|
+
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
module CouchRest
|
2
|
+
class Pager
|
3
|
+
attr_accessor :db
|
4
|
+
def initialize db
|
5
|
+
@db = db
|
6
|
+
end
|
7
|
+
|
8
|
+
def all_docs(count=100, &block)
|
9
|
+
startkey = nil
|
10
|
+
oldend = nil
|
11
|
+
|
12
|
+
while docrows = request_all_docs(count+1, startkey)
|
13
|
+
startkey = docrows.last['key']
|
14
|
+
docrows.pop if docrows.length > count
|
15
|
+
if oldend == startkey
|
16
|
+
break
|
17
|
+
end
|
18
|
+
yield(docrows)
|
19
|
+
oldend = startkey
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def key_reduce(view, count=2000, firstkey = nil, lastkey = nil, &block)
|
24
|
+
# start with no keys
|
25
|
+
startkey = firstkey
|
26
|
+
# lastprocessedkey = nil
|
27
|
+
keepgoing = true
|
28
|
+
|
29
|
+
while keepgoing && viewrows = request_view(view, count, startkey)
|
30
|
+
startkey = viewrows.first['key']
|
31
|
+
endkey = viewrows.last['key']
|
32
|
+
|
33
|
+
if (startkey == endkey)
|
34
|
+
# we need to rerequest to get a bigger page
|
35
|
+
# so we know we have all the rows for that key
|
36
|
+
viewrows = @db.view(view, :key => startkey)['rows']
|
37
|
+
# we need to do an offset thing to find the next startkey
|
38
|
+
# otherwise we just get stuck
|
39
|
+
lastdocid = viewrows.last['id']
|
40
|
+
fornextloop = @db.view(view, :startkey => startkey, :startkey_docid => lastdocid, :count => 2)['rows']
|
41
|
+
|
42
|
+
newendkey = fornextloop.last['key']
|
43
|
+
if (newendkey == endkey)
|
44
|
+
keepgoing = false
|
45
|
+
else
|
46
|
+
startkey = newendkey
|
47
|
+
end
|
48
|
+
rows = viewrows
|
49
|
+
else
|
50
|
+
rows = []
|
51
|
+
for r in viewrows
|
52
|
+
if (lastkey && r['key'] == lastkey)
|
53
|
+
keepgoing = false
|
54
|
+
break
|
55
|
+
end
|
56
|
+
break if (r['key'] == endkey)
|
57
|
+
rows << r
|
58
|
+
end
|
59
|
+
startkey = endkey
|
60
|
+
end
|
61
|
+
|
62
|
+
key = :begin
|
63
|
+
values = []
|
64
|
+
|
65
|
+
rows.each do |r|
|
66
|
+
if key != r['key']
|
67
|
+
# we're on a new key, yield the old first and then reset
|
68
|
+
yield(key, values) if key != :begin
|
69
|
+
key = r['key']
|
70
|
+
values = []
|
71
|
+
end
|
72
|
+
# keep accumulating
|
73
|
+
values << r['value']
|
74
|
+
end
|
75
|
+
yield(key, values)
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def request_all_docs count, startkey = nil
|
83
|
+
opts = {}
|
84
|
+
opts[:count] = count if count
|
85
|
+
opts[:startkey] = startkey if startkey
|
86
|
+
results = @db.documents(opts)
|
87
|
+
rows = results['rows']
|
88
|
+
rows unless rows.length == 0
|
89
|
+
end
|
90
|
+
|
91
|
+
def request_view view, count = nil, startkey = nil, endkey = nil
|
92
|
+
opts = {}
|
93
|
+
opts[:count] = count if count
|
94
|
+
opts[:startkey] = startkey if startkey
|
95
|
+
opts[:endkey] = endkey if endkey
|
96
|
+
|
97
|
+
results = @db.view(view, opts)
|
98
|
+
rows = results['rows']
|
99
|
+
rows unless rows.length == 0
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module CouchRest
|
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 = /^_/.match(name) ? "#{@db.root}/#{name}" : "#{@db.root}/_view/#{name}"
|
11
|
+
url = CouchRest.paramify_url urlst, params
|
12
|
+
# puts "stream #{url}"
|
13
|
+
first = nil
|
14
|
+
IO.popen("curl --silent #{url}") do |view|
|
15
|
+
first = view.gets # discard header
|
16
|
+
while line = view.gets
|
17
|
+
row = parse_line(line)
|
18
|
+
block.call row
|
19
|
+
end
|
20
|
+
end
|
21
|
+
parse_first(first)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def parse_line line
|
27
|
+
return nil unless line
|
28
|
+
if /(\{.*\}),?/.match(line.chomp)
|
29
|
+
JSON.parse($1)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def parse_first first
|
34
|
+
return nil unless first
|
35
|
+
parts = first.split(',')
|
36
|
+
parts.pop
|
37
|
+
line = parts.join(',')
|
38
|
+
JSON.parse("#{line}}")
|
39
|
+
rescue
|
40
|
+
nil
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
Couchapp will create a field on your document corresponding to any directories you make within the application directory, with the text of any files found as key/value pairs.
|
2
|
+
|
3
|
+
Also, any files that end in .json will be treated as json rather than text, and put in the corresponding field. Also note that file.json, file.js, or file.txt will be stored under the "file" key, so don't make collisions in the filesystem unless you want unpredictable results.
|
4
|
+
|
5
|
+
Of course you know that the views directory will be treated specially and -map and -reduce files will be mapped to the map and reduce functions. And the _attachments directory will be treated strangely as well.
|
6
|
+
|
7
|
+
doc.json is a special case, it is treated as json and its keys are applied to the document root; eg it does not result in a "doc" field on your design document. If you need a doc field on the design document, you'll have to define it with a doc.json like so: {"doc":"value for doc field"}
|
8
|
+
|
9
|
+
ps: each design document only has one language key: it will be set based on the file extensions in the views directory. CouchDB defaults to Javascript, so that's what you'll get if you don't define any views. You can override it with the doc.json file, but don't say we didn't warn you.
|
10
|
+
|
11
|
+
Oh yeah it's recommended that you delete this file.
|
@@ -0,0 +1,26 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>Generated CouchApp</title>
|
5
|
+
<link rel="stylesheet" href="screen.css" type="text/css">
|
6
|
+
</head>
|
7
|
+
<body>
|
8
|
+
<h1>Generated CouchApp</h1>
|
9
|
+
<ul id="view"></ul>
|
10
|
+
</body>
|
11
|
+
<script src="/_utils/script/json2.js"></script>
|
12
|
+
<script src="/_utils/script/jquery.js?1.2.6"></script>
|
13
|
+
<script src="/_utils/script/jquery.couch.js?0.8.0"></script>
|
14
|
+
<script type="text/javascript" charset="utf-8">
|
15
|
+
$(function() {
|
16
|
+
var dbname = document.location.href.split('/')[3];
|
17
|
+
var design = unescape(document.location.href).split('/')[5];
|
18
|
+
var DB = $.couch.db(dbname);
|
19
|
+
DB.view(design+"/example",{success: function(json) {
|
20
|
+
$("#view").html(json.rows.map(function(row) {
|
21
|
+
return '<li>'+row.key+'</li>';
|
22
|
+
}).join(''));
|
23
|
+
}});
|
24
|
+
});
|
25
|
+
</script>
|
26
|
+
</html>
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# This file must be loaded after the JSON gem and any other library that beats up the Time class.
|
2
|
+
class Time
|
3
|
+
# This date format sorts lexicographically
|
4
|
+
# and is compatible with Javascript's <tt>new Date(time_string)</tt> constructor.
|
5
|
+
# Note this this format stores all dates in UTC so that collation
|
6
|
+
# order is preserved. (There's no longer a need to set <tt>ENV['TZ'] = 'UTC'</tt>
|
7
|
+
# in your application.)
|
8
|
+
|
9
|
+
def to_json(options = nil)
|
10
|
+
u = self.utc
|
11
|
+
%("#{u.strftime("%Y/%m/%d %H:%M:%S +0000")}")
|
12
|
+
end
|
13
|
+
|
14
|
+
# Decodes the JSON time format to a UTC time.
|
15
|
+
# Based on Time.parse from ActiveSupport. ActiveSupport's version
|
16
|
+
# is more complete, returning a time in your current timezone,
|
17
|
+
# rather than keeping the time in UTC. YMMV.
|
18
|
+
# def self.parse string, fallback=nil
|
19
|
+
# d = DateTime.parse(string).new_offset
|
20
|
+
# self.utc(d.year, d.month, d.day, d.hour, d.min, d.sec)
|
21
|
+
# rescue
|
22
|
+
# fallback
|
23
|
+
# end
|
24
|
+
end
|
data/lib/couchrest.rb
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
# Copyright 2008 J. Chris Anderson
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
require "rubygems"
|
16
|
+
require 'json'
|
17
|
+
require 'rest_client'
|
18
|
+
# require 'extlib'
|
19
|
+
|
20
|
+
$:.unshift File.dirname(__FILE__) unless
|
21
|
+
$:.include?(File.dirname(__FILE__)) ||
|
22
|
+
$:.include?(File.expand_path(File.dirname(__FILE__)))
|
23
|
+
|
24
|
+
|
25
|
+
require 'couchrest/monkeypatches'
|
26
|
+
|
27
|
+
# = CouchDB, close to the metal
|
28
|
+
module CouchRest
|
29
|
+
autoload :Server, 'couchrest/core/server'
|
30
|
+
autoload :Database, 'couchrest/core/database'
|
31
|
+
autoload :Document, 'couchrest/core/document'
|
32
|
+
autoload :Design, 'couchrest/core/design'
|
33
|
+
autoload :View, 'couchrest/core/view'
|
34
|
+
autoload :Model, 'couchrest/core/model'
|
35
|
+
autoload :Pager, 'couchrest/helper/pager'
|
36
|
+
autoload :FileManager, 'couchrest/helper/file_manager'
|
37
|
+
autoload :Streamer, 'couchrest/helper/streamer'
|
38
|
+
|
39
|
+
# The CouchRest module methods handle the basic JSON serialization
|
40
|
+
# and deserialization, as well as query parameters. The module also includes
|
41
|
+
# some helpers for tasks like instantiating a new Database or Server instance.
|
42
|
+
class << self
|
43
|
+
|
44
|
+
# todo, make this parse the url and instantiate a Server or Database instance
|
45
|
+
# depending on the specificity.
|
46
|
+
def new(*opts)
|
47
|
+
Server.new(*opts)
|
48
|
+
end
|
49
|
+
|
50
|
+
def parse url
|
51
|
+
case url
|
52
|
+
when /^http:\/\/(.*)\/(.*)\/(.*)/
|
53
|
+
host = $1
|
54
|
+
db = $2
|
55
|
+
docid = $3
|
56
|
+
when /^http:\/\/(.*)\/(.*)/
|
57
|
+
host = $1
|
58
|
+
db = $2
|
59
|
+
when /^http:\/\/(.*)/
|
60
|
+
host = $1
|
61
|
+
when /(.*)\/(.*)\/(.*)/
|
62
|
+
host = $1
|
63
|
+
db = $2
|
64
|
+
docid = $3
|
65
|
+
when /(.*)\/(.*)/
|
66
|
+
host = $1
|
67
|
+
db = $2
|
68
|
+
else
|
69
|
+
db = url
|
70
|
+
end
|
71
|
+
|
72
|
+
db = nil if db && db.empty?
|
73
|
+
|
74
|
+
{
|
75
|
+
:host => host || "localhost:5984",
|
76
|
+
:database => db,
|
77
|
+
:doc => docid
|
78
|
+
}
|
79
|
+
end
|
80
|
+
|
81
|
+
# ensure that a database exists
|
82
|
+
# creates it if it isn't already there
|
83
|
+
# returns it after it's been created
|
84
|
+
def database! url
|
85
|
+
parsed = parse url
|
86
|
+
cr = CouchRest.new(parsed[:host])
|
87
|
+
cr.database!(parsed[:database])
|
88
|
+
end
|
89
|
+
|
90
|
+
def database url
|
91
|
+
parsed = parse url
|
92
|
+
cr = CouchRest.new(parsed[:host])
|
93
|
+
cr.database(parsed[:database])
|
94
|
+
end
|
95
|
+
|
96
|
+
def put uri, doc = nil
|
97
|
+
payload = doc.to_json if doc
|
98
|
+
JSON.parse(RestClient.put(uri, payload))
|
99
|
+
end
|
100
|
+
|
101
|
+
def get uri
|
102
|
+
JSON.parse(RestClient.get(uri), :max_nesting => false)
|
103
|
+
end
|
104
|
+
|
105
|
+
def post uri, doc = nil
|
106
|
+
payload = doc.to_json if doc
|
107
|
+
JSON.parse(RestClient.post(uri, payload))
|
108
|
+
end
|
109
|
+
|
110
|
+
def delete uri
|
111
|
+
JSON.parse(RestClient.delete(uri))
|
112
|
+
end
|
113
|
+
|
114
|
+
def paramify_url url, params = {}
|
115
|
+
if params && !params.empty?
|
116
|
+
query = params.collect do |k,v|
|
117
|
+
v = v.to_json if %w{key startkey endkey}.include?(k.to_s)
|
118
|
+
"#{k}=#{CGI.escape(v.to_s)}"
|
119
|
+
end.join("&")
|
120
|
+
url = "#{url}?#{query}"
|
121
|
+
end
|
122
|
+
url
|
123
|
+
end
|
124
|
+
end # class << self
|
125
|
+
end
|