couch_crumbs 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|