couch_crumbs 0.0.1
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/History.txt +4 -0
- data/Manifest.txt +29 -0
- data/README.rdoc +131 -0
- data/Rakefile +37 -0
- data/lib/core_ext/array.rb +9 -0
- data/lib/couch_crumbs/database.rb +68 -0
- data/lib/couch_crumbs/design.rb +129 -0
- data/lib/couch_crumbs/document.rb +471 -0
- data/lib/couch_crumbs/json/all.json +5 -0
- data/lib/couch_crumbs/json/children.json +5 -0
- data/lib/couch_crumbs/json/design.json +5 -0
- data/lib/couch_crumbs/json/simple.json +5 -0
- data/lib/couch_crumbs/query.rb +95 -0
- data/lib/couch_crumbs/server.rb +45 -0
- data/lib/couch_crumbs/view.rb +73 -0
- data/lib/couch_crumbs.rb +45 -0
- data/script/console +11 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/spec/core_ext/array_spec.rb +21 -0
- data/spec/couch_crumbs/database_spec.rb +69 -0
- data/spec/couch_crumbs/design_spec.rb +103 -0
- data/spec/couch_crumbs/document_spec.rb +473 -0
- data/spec/couch_crumbs/server_spec.rb +63 -0
- data/spec/couch_crumbs/view_spec.rb +41 -0
- data/spec/couch_crumbs_spec.rb +18 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +20 -0
- data/tasks/rspec.rake +21 -0
- metadata +105 -0
@@ -0,0 +1,471 @@
|
|
1
|
+
require "facets/string"
|
2
|
+
require "english/inflect"
|
3
|
+
|
4
|
+
module CouchCrumbs
|
5
|
+
|
6
|
+
# Document is an abstract base module that you mixin to your own classes
|
7
|
+
# to gain access to CouchDB document instances.
|
8
|
+
#
|
9
|
+
module Document
|
10
|
+
|
11
|
+
module InstanceMethods
|
12
|
+
|
13
|
+
include CouchCrumbs::Query
|
14
|
+
|
15
|
+
# Return the class-based database
|
16
|
+
def database
|
17
|
+
self.class.database
|
18
|
+
end
|
19
|
+
|
20
|
+
# Return document id (typically a UUID)
|
21
|
+
#
|
22
|
+
def id
|
23
|
+
raw["_id"]
|
24
|
+
end
|
25
|
+
|
26
|
+
# Set the document id
|
27
|
+
def id=(new_id)
|
28
|
+
raise "only new documents may set an id" unless new_document?
|
29
|
+
|
30
|
+
raw["_id"] = new_id
|
31
|
+
end
|
32
|
+
|
33
|
+
# Return document revision
|
34
|
+
#
|
35
|
+
def rev
|
36
|
+
raw["_rev"]
|
37
|
+
end
|
38
|
+
|
39
|
+
# Return the CouchCrumb document type
|
40
|
+
#
|
41
|
+
def crumb_type
|
42
|
+
raw["crumb_type"]
|
43
|
+
end
|
44
|
+
|
45
|
+
# Save a document to a database
|
46
|
+
#
|
47
|
+
def save!
|
48
|
+
raise "unable to save frozen documents" if frozen?
|
49
|
+
|
50
|
+
# Before Callback
|
51
|
+
before_save
|
52
|
+
|
53
|
+
# Update timestamps
|
54
|
+
raw["updated_at"] = Time.now if self.class.properties.include?(:updated_at)
|
55
|
+
|
56
|
+
# Save to the DB
|
57
|
+
result = JSON.parse(RestClient.put(uri, raw.to_json))
|
58
|
+
|
59
|
+
# Update ID and Rev properties
|
60
|
+
raw["_id"] = result["id"]
|
61
|
+
raw["_rev"] = result["rev"]
|
62
|
+
|
63
|
+
# After callback
|
64
|
+
after_save
|
65
|
+
|
66
|
+
result["ok"]
|
67
|
+
end
|
68
|
+
|
69
|
+
# Update and save the named properties
|
70
|
+
#
|
71
|
+
def update_attributes!(attributes = {})
|
72
|
+
attributes.each_pair do |key, value|
|
73
|
+
raw[key.to_s] = value
|
74
|
+
end
|
75
|
+
|
76
|
+
save!
|
77
|
+
end
|
78
|
+
|
79
|
+
# Return true prior to document being saved
|
80
|
+
#
|
81
|
+
def new_document?
|
82
|
+
raw["_rev"].eql?(nil)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Remove document from the database
|
86
|
+
#
|
87
|
+
def destroy!
|
88
|
+
before_destroy
|
89
|
+
|
90
|
+
freeze
|
91
|
+
|
92
|
+
# destruction status
|
93
|
+
status = nil
|
94
|
+
|
95
|
+
# Since new documents haven't been saved yet, and frozen documents
|
96
|
+
# *can't* be saved, simply return true here.
|
97
|
+
if new_document?
|
98
|
+
status = true
|
99
|
+
else
|
100
|
+
result = JSON.parse(RestClient.delete(File.join(uri, "?rev=#{ rev }")))
|
101
|
+
|
102
|
+
status = result["ok"]
|
103
|
+
end
|
104
|
+
|
105
|
+
after_destroy
|
106
|
+
|
107
|
+
status
|
108
|
+
end
|
109
|
+
|
110
|
+
# Hook called after a document has been initialized
|
111
|
+
#
|
112
|
+
def after_initialize
|
113
|
+
nil
|
114
|
+
end
|
115
|
+
|
116
|
+
# Hook called during #create! before a document is #saved!
|
117
|
+
#
|
118
|
+
def before_create
|
119
|
+
nil
|
120
|
+
end
|
121
|
+
|
122
|
+
# Hook called during #create! after a document has #saved!
|
123
|
+
#
|
124
|
+
def after_create
|
125
|
+
nil
|
126
|
+
end
|
127
|
+
|
128
|
+
# Hook called during #save! before a document has #saved!
|
129
|
+
#
|
130
|
+
def before_save
|
131
|
+
nil
|
132
|
+
end
|
133
|
+
|
134
|
+
# Hook called during #save! after a document has #saved!
|
135
|
+
#
|
136
|
+
def after_save
|
137
|
+
nil
|
138
|
+
end
|
139
|
+
|
140
|
+
# Hook called during #destroy! before a document has been destroyed
|
141
|
+
#
|
142
|
+
def before_destroy
|
143
|
+
nil
|
144
|
+
end
|
145
|
+
|
146
|
+
# Hook called during #destroy! after a document has been destroyed
|
147
|
+
#
|
148
|
+
def after_destroy
|
149
|
+
nil
|
150
|
+
end
|
151
|
+
|
152
|
+
end
|
153
|
+
|
154
|
+
module ClassMethods
|
155
|
+
|
156
|
+
include CouchCrumbs::Query
|
157
|
+
|
158
|
+
# Return the useful portion of module/class type
|
159
|
+
# @todo cache crumb_type on the including base class
|
160
|
+
#
|
161
|
+
def crumb_type
|
162
|
+
class_variable_get(:@@crumb_type)
|
163
|
+
end
|
164
|
+
|
165
|
+
# Return the database to use for this class
|
166
|
+
#
|
167
|
+
def database
|
168
|
+
class_variable_get(:@@database)
|
169
|
+
end
|
170
|
+
|
171
|
+
# Set the database that documents of this type will use (will create
|
172
|
+
# a new database if name does not exist)
|
173
|
+
#
|
174
|
+
def use_database(name)
|
175
|
+
class_variable_set(:@@database, Database.new(:name => name))
|
176
|
+
end
|
177
|
+
|
178
|
+
# Return all named properties for this document type
|
179
|
+
#
|
180
|
+
def properties
|
181
|
+
class_variable_get(:@@properties)
|
182
|
+
end
|
183
|
+
|
184
|
+
# Add a named property to a document type
|
185
|
+
#
|
186
|
+
def property(name, opts = {})
|
187
|
+
name = name.to_sym
|
188
|
+
properties << name
|
189
|
+
|
190
|
+
class_eval do
|
191
|
+
# getter
|
192
|
+
define_method(name) do
|
193
|
+
raw[name.to_s]
|
194
|
+
end
|
195
|
+
# setter
|
196
|
+
define_method("#{ name }=".to_sym) do |new_value|
|
197
|
+
raw[name.to_s] = new_value
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
# Append default timestamps as named properties
|
203
|
+
# @todo - add :created_at as a read-only property
|
204
|
+
#
|
205
|
+
def timestamps!
|
206
|
+
[:created_at, :updated_at].each do |name|
|
207
|
+
property(name)
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
# Return the design doc for this class
|
212
|
+
#
|
213
|
+
def design_doc
|
214
|
+
Design.get!(database, :name => crumb_type)
|
215
|
+
end
|
216
|
+
|
217
|
+
# Return an array of all views for this class
|
218
|
+
#
|
219
|
+
def views(opts = {})
|
220
|
+
design_doc.views(opts)
|
221
|
+
end
|
222
|
+
|
223
|
+
# Create a default view on a given property, returning documents
|
224
|
+
#
|
225
|
+
def doc_view(*args)
|
226
|
+
# Get the design doc for this document type
|
227
|
+
design = design_doc
|
228
|
+
|
229
|
+
# Create simple views for the named properties
|
230
|
+
args.each do |prop|
|
231
|
+
view = View.create!(design, prop.to_s, View.simple_json(crumb_type, prop))
|
232
|
+
|
233
|
+
self.class.instance_eval do
|
234
|
+
define_method("by_#{ prop }".to_sym) do |opts|
|
235
|
+
query_docs(view.uri, {:descending => false}.merge(opts||{})).collect do |doc|
|
236
|
+
if doc["crumb_type"]
|
237
|
+
new(:hash => doc)
|
238
|
+
else
|
239
|
+
warn "skipping unknown document: #{ document }"
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
nil
|
247
|
+
end
|
248
|
+
|
249
|
+
# Create an advanced view from a given :template
|
250
|
+
#
|
251
|
+
def custom_view(opts = {})
|
252
|
+
raise ArgumentError.new("opts must contain a :name key") unless opts.has_key?(:name)
|
253
|
+
raise ArgumentError.new("opts must contain a :template key") unless opts.has_key?(:template)
|
254
|
+
|
255
|
+
view = View.create!(design_doc, opts[:name], View.advanced_json(opts[:template], opts))
|
256
|
+
|
257
|
+
self.class.instance_eval do
|
258
|
+
define_method("#{ opts[:name] }".to_sym) do
|
259
|
+
if view.has_reduce?
|
260
|
+
query_values(view.uri)
|
261
|
+
else
|
262
|
+
query_docs(view.uri, :descending => false).collect do |doc|
|
263
|
+
if doc["crumb_type"]
|
264
|
+
new(:hash => doc)
|
265
|
+
else
|
266
|
+
warn "skipping unknown document: #{ doc }"
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
# Create and save a new document
|
275
|
+
# @todo - add before_create and after_create callbacks
|
276
|
+
#
|
277
|
+
def create!(opts = {})
|
278
|
+
document = new(opts)
|
279
|
+
|
280
|
+
yield document if block_given?
|
281
|
+
|
282
|
+
document.before_create
|
283
|
+
|
284
|
+
document.save!
|
285
|
+
|
286
|
+
document.after_create
|
287
|
+
|
288
|
+
document
|
289
|
+
end
|
290
|
+
|
291
|
+
# Return a specific document given an exact id
|
292
|
+
#
|
293
|
+
def get!(id)
|
294
|
+
raise ArgumentError.new("id must not be blank") if id.empty? or id.nil?
|
295
|
+
|
296
|
+
json = RestClient.get(File.join(database.uri, id))
|
297
|
+
|
298
|
+
result = JSON.parse(json)
|
299
|
+
|
300
|
+
document = new(
|
301
|
+
:json => json
|
302
|
+
)
|
303
|
+
|
304
|
+
document
|
305
|
+
end
|
306
|
+
|
307
|
+
# Return an array of all documents of this type
|
308
|
+
#
|
309
|
+
def all(opts = {})
|
310
|
+
# Add the #all method
|
311
|
+
view = design_doc.views(:name => "all")
|
312
|
+
|
313
|
+
query_docs("#{ view.uri }".downcase, opts).collect do |doc|
|
314
|
+
if doc["crumb_type"]
|
315
|
+
get!(doc["_id"])
|
316
|
+
else
|
317
|
+
warn "skipping unknown document: #{ doc }"
|
318
|
+
|
319
|
+
nil
|
320
|
+
end
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
# Like parent_document :person
|
325
|
+
#
|
326
|
+
def parent_document(model, opts = {})
|
327
|
+
model = model.to_s.downcase
|
328
|
+
|
329
|
+
property("#{ model }_parent_id")
|
330
|
+
|
331
|
+
begin
|
332
|
+
parent_class = eval(model.modulize)
|
333
|
+
rescue
|
334
|
+
require "#{ model.methodize }.rb"
|
335
|
+
retry
|
336
|
+
end
|
337
|
+
|
338
|
+
self.class_eval do
|
339
|
+
define_method(model.to_sym) do
|
340
|
+
parent_class.get!(raw["#{ model }_parent_id"])
|
341
|
+
end
|
342
|
+
|
343
|
+
define_method("#{ model }=".to_sym) do |new_parent|
|
344
|
+
raise ArgumentError.new("parent documents must be saved before children") if new_parent.new_document?
|
345
|
+
|
346
|
+
raw["#{ model }_parent_id"] = new_parent.id
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
nil
|
351
|
+
end
|
352
|
+
|
353
|
+
# Like child_document :address
|
354
|
+
#
|
355
|
+
def child_document(model, opts = {})
|
356
|
+
model = model.to_s.downcase
|
357
|
+
|
358
|
+
property("#{ model }_child_id")
|
359
|
+
|
360
|
+
begin
|
361
|
+
child_class = eval(model.modulize)
|
362
|
+
rescue
|
363
|
+
require "#{ model.methodize }.rb"
|
364
|
+
retry
|
365
|
+
end
|
366
|
+
|
367
|
+
self.class_eval do
|
368
|
+
define_method(model.to_sym) do
|
369
|
+
child_class.get!(raw["#{ model }_child_id"])
|
370
|
+
end
|
371
|
+
|
372
|
+
define_method("#{ model }=".to_sym) do |new_child|
|
373
|
+
raise ArgumentError.new("parent documents must be saved before adding children") if new_document?
|
374
|
+
|
375
|
+
raw["#{ model }_child_id"] = new_child.id
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
379
|
+
nil
|
380
|
+
end
|
381
|
+
|
382
|
+
# Like has_many :projects
|
383
|
+
#
|
384
|
+
def child_documents(model, opts = {})
|
385
|
+
model = model.to_s.downcase
|
386
|
+
|
387
|
+
begin
|
388
|
+
child_class = eval(model.modulize)
|
389
|
+
rescue
|
390
|
+
require "#{ model.methodize }.rb"
|
391
|
+
retry
|
392
|
+
end
|
393
|
+
|
394
|
+
# Add a view to the child class
|
395
|
+
View.create!(child_class.design_doc, "#{ crumb_type }_parent_id", View.advanced_json(File.join(File.dirname(__FILE__), "json", "children.json"), :parent => self.crumb_type, :child => model))
|
396
|
+
|
397
|
+
# Add a method to access the model's new view
|
398
|
+
self.class_eval do
|
399
|
+
define_method(English::Inflect.plural(model)) do
|
400
|
+
query_docs(eval(model.modulize).views(:name => "#{ self.class.crumb_type }_parent_id").uri).collect do |doc|
|
401
|
+
child_class.get!(doc["_id"])
|
402
|
+
end
|
403
|
+
end
|
404
|
+
|
405
|
+
define_method("add_#{ model }") do |new_child|
|
406
|
+
new_child.send("#{ self.class.crumb_type }_parent_id=", self.id)
|
407
|
+
new_child.save!
|
408
|
+
end
|
409
|
+
end
|
410
|
+
|
411
|
+
nil
|
412
|
+
end
|
413
|
+
|
414
|
+
end
|
415
|
+
|
416
|
+
# Mixin our document methods
|
417
|
+
#
|
418
|
+
def self.included(base)
|
419
|
+
base.send(:include, InstanceMethods)
|
420
|
+
base.extend(ClassMethods)
|
421
|
+
# Override #initialize
|
422
|
+
base.class_eval do
|
423
|
+
|
424
|
+
# Set class variables
|
425
|
+
class_variable_set(:@@crumb_type, base.name.split('::').last.downcase)
|
426
|
+
class_variable_set(:@@database, CouchCrumbs::default_database)
|
427
|
+
class_variable_set(:@@properties, [])
|
428
|
+
|
429
|
+
# Accessors
|
430
|
+
attr_accessor :uri, :raw
|
431
|
+
|
432
|
+
# Override document #initialize
|
433
|
+
def initialize(opts = {})
|
434
|
+
raise ArgumentError.new("opts must be hash-like: #{ opts }") unless opts.respond_to?(:[])
|
435
|
+
|
436
|
+
# If :json is present, we just parse it as an existing document
|
437
|
+
if opts[:json]
|
438
|
+
self.raw = JSON.parse(opts[:json])
|
439
|
+
elsif opts[:hash]
|
440
|
+
self.raw = opts[:hash]
|
441
|
+
else
|
442
|
+
self.raw = {}
|
443
|
+
|
444
|
+
# Init special values
|
445
|
+
raw["_id"] = opts[:id] || database.server.uuids
|
446
|
+
raw["_rev"] = opts[:rev] unless opts[:rev].eql?(nil)
|
447
|
+
raw["crumb_type"] = self.class.crumb_type
|
448
|
+
raw["created_at"] = Time.now if self.class.properties.include?(:created_at)
|
449
|
+
|
450
|
+
# Init named properties
|
451
|
+
opts.each_pair do |name, value|
|
452
|
+
send("#{ name }=", value)
|
453
|
+
end
|
454
|
+
end
|
455
|
+
|
456
|
+
# This specific CouchDB document URI
|
457
|
+
self.uri = File.join(database.uri, id)
|
458
|
+
|
459
|
+
# Callback
|
460
|
+
after_initialize
|
461
|
+
end
|
462
|
+
|
463
|
+
end
|
464
|
+
|
465
|
+
# Create an advanced "all" view
|
466
|
+
View.create!(base.design_doc, "all", View.advanced_json(File.join(File.dirname(__FILE__), "json", "all.json"), :crumb_type => base.crumb_type))
|
467
|
+
end
|
468
|
+
|
469
|
+
end
|
470
|
+
|
471
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module CouchCrumbs
|
2
|
+
|
3
|
+
# Mixin to query databases and views
|
4
|
+
module Query
|
5
|
+
|
6
|
+
# Query an URI with opts and return an array of ruby hashes
|
7
|
+
# representing JSON docs.
|
8
|
+
#
|
9
|
+
# === Parameters (see: http://wiki.apache.org/couchdb/HTTP_view_API)
|
10
|
+
# key=keyvalue
|
11
|
+
# startkey=keyvalue
|
12
|
+
# startkey_docid=docid
|
13
|
+
# endkey=keyvalue
|
14
|
+
# endkey_docid=docid
|
15
|
+
# limit=max rows to return This used to be called "count" previous to Trunk SVN r731159
|
16
|
+
# stale=ok
|
17
|
+
# descending=true
|
18
|
+
# skip=number of rows to skip (very slow)
|
19
|
+
# group=true Version 0.8.0 and forward
|
20
|
+
# group_level=int
|
21
|
+
# reduce=false Trunk only (0.9)
|
22
|
+
# include_docs=true Trunk only (0.9)
|
23
|
+
#
|
24
|
+
def query_docs(uri, opts = {})
|
25
|
+
opts = {} unless opts
|
26
|
+
|
27
|
+
# Build our view query string
|
28
|
+
query_params = "?"
|
29
|
+
|
30
|
+
if opts.has_key?(:key)
|
31
|
+
query_params << %(key="#{ opts.delete(:key) }")
|
32
|
+
elsif opts.has_key?(:startkey)
|
33
|
+
query_params << %(startkey="#{ opts.delete(:startkey) }")
|
34
|
+
if opts.has_key?(:startkey_docid)
|
35
|
+
query_params << %(&startkey_docid="#{ opts.delete(:startkey_docid) }")
|
36
|
+
end
|
37
|
+
if opts.has_key?(:endkey)
|
38
|
+
query_params << %(&endkey="#{ opts.delete(:endkey) }")
|
39
|
+
if opts.has_key?(:endkey_docid)
|
40
|
+
query_params << %(&endkey_docid="#{ opts.delete(:endkey_docid) }")
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Escape the quoted JSON query keys
|
46
|
+
query_params = URI::escape(query_params)
|
47
|
+
|
48
|
+
# Default options
|
49
|
+
(@@default_options ||= {
|
50
|
+
:limit => 25, # limit => 0 will return metadata only
|
51
|
+
:stale => false,
|
52
|
+
:descending => false,
|
53
|
+
:skip => nil, # The skip option should only be used with small values
|
54
|
+
:group => nil,
|
55
|
+
:group_level => nil,
|
56
|
+
:include_docs => true
|
57
|
+
}).merge(opts).each do |key, value|
|
58
|
+
query_params << %(&#{ key }=#{ value }) if value
|
59
|
+
end
|
60
|
+
|
61
|
+
query_string = "#{ uri }#{ query_params }"
|
62
|
+
|
63
|
+
# Query the server and return an array of documents (will include design docs)
|
64
|
+
JSON.parse(RestClient.get(query_string))["rows"].collect do |row|
|
65
|
+
row["doc"]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# For querying views with a reduce function or other value-based views
|
70
|
+
# opts => :raw will return the raw view result, otherwise we try to
|
71
|
+
# extract a value
|
72
|
+
#
|
73
|
+
def query_values(uri, opts = {})
|
74
|
+
query_params = "?"
|
75
|
+
|
76
|
+
opts.each do |key, value|
|
77
|
+
query_params << %(&#{ key }=#{ value }) if value
|
78
|
+
end
|
79
|
+
|
80
|
+
query_string = "#{ uri }#{ query_params }"
|
81
|
+
|
82
|
+
result = JSON.parse(RestClient.get(query_string))
|
83
|
+
|
84
|
+
# Extract "value" key/value
|
85
|
+
if opts[:raw]
|
86
|
+
result
|
87
|
+
else
|
88
|
+
result["rows"].first["value"]
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require "rest_client"
|
2
|
+
require "json"
|
3
|
+
|
4
|
+
module CouchCrumbs
|
5
|
+
|
6
|
+
# Represents an instance of a live running CouchDB server
|
7
|
+
#
|
8
|
+
class Server
|
9
|
+
|
10
|
+
DEFAULT_URI = "http://couchdb.local:5984".freeze
|
11
|
+
|
12
|
+
attr_accessor :uri, :status
|
13
|
+
|
14
|
+
# Create a new instance of Server
|
15
|
+
#
|
16
|
+
def initialize(opts = {})
|
17
|
+
self.uri = opts[:uri] || DEFAULT_URI
|
18
|
+
|
19
|
+
self.status = JSON.parse(RestClient.get(self.uri))
|
20
|
+
end
|
21
|
+
|
22
|
+
# Return an array of databases
|
23
|
+
# @todo - add a :refresh argument with a 10 second cache of the DBs
|
24
|
+
#
|
25
|
+
def databases
|
26
|
+
JSON.parse(RestClient.get(File.join(self.uri, "_all_dbs"))).collect do |database_name|
|
27
|
+
Database.new(:name => database_name)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Return a new random UUID for use in documents
|
32
|
+
#
|
33
|
+
def uuids(count = 1)
|
34
|
+
uuids = JSON.parse(RestClient.get(File.join(self.uri, "_uuids?count=#{ count }")))["uuids"]
|
35
|
+
|
36
|
+
if count > 1
|
37
|
+
uuids
|
38
|
+
else
|
39
|
+
uuids.first
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module CouchCrumbs
|
2
|
+
|
3
|
+
# Based on the raw JSON that make up each view in a design doc.
|
4
|
+
#
|
5
|
+
class View
|
6
|
+
|
7
|
+
include CouchCrumbs::Query
|
8
|
+
|
9
|
+
attr_accessor :raw, :uri, :name
|
10
|
+
|
11
|
+
# Return or create a new view object
|
12
|
+
#
|
13
|
+
def initialize(design, name, json)
|
14
|
+
self.name = name
|
15
|
+
self.uri = File.join(design.uri, "_view", name)
|
16
|
+
self.raw = JSON.parse(json)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Create a new view and save the containing design doc
|
20
|
+
def self.create!(design, name, json)
|
21
|
+
view = new(design, name, json)
|
22
|
+
|
23
|
+
design.add_view(view)
|
24
|
+
|
25
|
+
design.save!
|
26
|
+
|
27
|
+
view
|
28
|
+
end
|
29
|
+
|
30
|
+
# Return a view as a JSON hash
|
31
|
+
#
|
32
|
+
def self.simple_json(type, property)
|
33
|
+
# Read the 'simple' template (stripping newlines and tabs)
|
34
|
+
template = File.read(File.join(File.dirname(__FILE__), "json", "simple.json")).gsub!(/(\n|\r|\t)/, '')
|
35
|
+
|
36
|
+
template.gsub!(/\#name/, property.to_s.downcase)
|
37
|
+
template.gsub!(/\#crumb_type/, type.to_s)
|
38
|
+
template.gsub!(/\#property/, property.to_s.downcase)
|
39
|
+
|
40
|
+
template
|
41
|
+
end
|
42
|
+
|
43
|
+
# Return an advanced view as a JSON hash
|
44
|
+
# template => path to a .json template
|
45
|
+
# opts => options to gsub into the template
|
46
|
+
#
|
47
|
+
def self.advanced_json(template, opts = {})
|
48
|
+
# Read the given template (strip newlines to avoid JSON parser errors)
|
49
|
+
template = File.read(template).gsub(/(\n|\r|\t|\s{2,})/, '')
|
50
|
+
|
51
|
+
# Sub in any opts
|
52
|
+
opts.each do |key, value|
|
53
|
+
template.gsub!(/\##{ key }/, value.to_s)
|
54
|
+
end
|
55
|
+
|
56
|
+
template
|
57
|
+
end
|
58
|
+
|
59
|
+
# Return a unique hash of the raw json
|
60
|
+
#
|
61
|
+
def hash
|
62
|
+
raw.hash
|
63
|
+
end
|
64
|
+
|
65
|
+
# Return true if this view will reduce values
|
66
|
+
#
|
67
|
+
def has_reduce?
|
68
|
+
raw[raw.keys.first].has_key?("reduce")
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|