chill 6 → 8
Sign up to get free protection for your applications and to get access to all the features.
- data/library/chill.rb +394 -61
- metadata +24 -9
data/library/chill.rb
CHANGED
@@ -4,7 +4,30 @@ require 'uuid'
|
|
4
4
|
require 'rest-client'
|
5
5
|
require 'uri'
|
6
6
|
|
7
|
+
# The main ChillDB module - This is where it all starts
|
8
|
+
#
|
9
|
+
# Throughout these docs you'll find classes under ChillDB. In a real
|
10
|
+
# application you'll call <tt>ChillDB.goes :SomethingOrOther</tt> and from
|
11
|
+
# then on, substitute <tt>ChillDB</tt> for <tt>SomethingOrOther</tt> when
|
12
|
+
# refering to class names. ChillDB effectively creates a copy of itself linked
|
13
|
+
# to a database called SomethingOrOther on the local couchdb server. Check out
|
14
|
+
# ChillDB.goes for more details on getting started. Throughout the
|
15
|
+
# documentation, you'll see KittensApp as a placeholder - imagine
|
16
|
+
# <tt>ChillDB.goes :KittensApp</tt> has been run beforehand.
|
7
17
|
module ChillDB
|
18
|
+
# Creates a copy of ChillDB linked to a database named with the first
|
19
|
+
# argument. You can also provide host, port, user, and pass options as a
|
20
|
+
# hash to connect chill to a remote couchdb server or just provide
|
21
|
+
# authentication info. Once ChillDB.goes has been called, you'll use the
|
22
|
+
# database_name instead of ChillDB when refering to chill's classes
|
23
|
+
# throughout your app.
|
24
|
+
#
|
25
|
+
# Example:
|
26
|
+
# ChillDB.goes :KittensApp
|
27
|
+
#
|
28
|
+
# # load 'frederick' from the KittensApp database
|
29
|
+
# # on the locally installed couch server
|
30
|
+
# KittensApp['frederick'] #=> <ChillDB::Document>
|
8
31
|
def self.goes database_name, *args
|
9
32
|
submod = Module.new do
|
10
33
|
extend ChillDB
|
@@ -17,25 +40,55 @@ module ChillDB
|
|
17
40
|
Object.const_set(database_name, submod)
|
18
41
|
end
|
19
42
|
|
20
|
-
# stores a
|
43
|
+
# #templates stores a collection of new document templates. This is a handy
|
44
|
+
# shortcut to hold your different types of documents. Templates
|
45
|
+
# automatically have a property called 'kind' which is assigned to the
|
46
|
+
# template's name, which can be handy when writing views. They also provide
|
47
|
+
# a great place to write in default values, and are just generally handy as
|
48
|
+
# a place to keep a little note to yourself what fields you might expect in
|
49
|
+
# a type of document.
|
50
|
+
#
|
51
|
+
# Example:
|
52
|
+
# KittensApp.templates(
|
53
|
+
# cat: {
|
54
|
+
# color: 'unknown',
|
55
|
+
# softness: 5,
|
56
|
+
# likes: ['exploding', 'cupcakes'],
|
57
|
+
# dislikes: ['dark matter']
|
58
|
+
# }
|
59
|
+
# )
|
60
|
+
#
|
61
|
+
# # use the template, extend it with specific info, and save it!
|
62
|
+
# new_cat = KittensApp.template(:cat).merge(
|
63
|
+
# color: 'octarine',
|
64
|
+
# softness: 13,
|
65
|
+
# _id: 'bjorn'
|
66
|
+
# ).commit!
|
21
67
|
def templates obj
|
22
68
|
@@templates.merge!(obj) if obj and obj.is_a? Hash
|
23
69
|
return @@templates
|
24
70
|
end
|
25
71
|
|
26
|
-
#
|
72
|
+
# Gets a copy of a template previously defined using #templates. further
|
73
|
+
# info on usage is in the description of #templates.
|
27
74
|
def template kind
|
28
|
-
properties = @@templates[kind].dup
|
75
|
+
properties = @@templates[kind.to_sym].dup
|
29
76
|
properties[:kind] = kind.to_s
|
30
77
|
ChillDB::Document.new(@@database, properties)
|
31
78
|
end
|
32
79
|
|
33
|
-
#
|
80
|
+
# Loads or creates a new ChillDB::Design with a specified name. Designs are
|
81
|
+
# used to create views, which create cached high speed document indexes for
|
82
|
+
# searching, sorting, and calculating.
|
34
83
|
def design name
|
35
84
|
ChillDB::Design.new @@database, name
|
36
85
|
end
|
37
86
|
|
38
|
-
#
|
87
|
+
# Loads or creates a document with a specified _id. If no _id is specified
|
88
|
+
# a new blank document is created which will be assigned a fresh UUID as
|
89
|
+
# it's _id when saved unless you specify one before committing it.
|
90
|
+
#
|
91
|
+
# Returns a ChillDB::Document
|
39
92
|
def document id = false
|
40
93
|
if id.respond_to? :to_ary
|
41
94
|
list = id.to_ary.map { |i| i.to_s }
|
@@ -49,6 +102,16 @@ module ChillDB
|
|
49
102
|
end
|
50
103
|
alias_method :[], :document
|
51
104
|
|
105
|
+
# Commit a ChillDB::Document or a Hash to the database with a specific _id.
|
106
|
+
# This method is useful for quickly storing bits of information. For more
|
107
|
+
# involved applications, using ChillDB::Document objects directly is
|
108
|
+
# best.
|
109
|
+
#
|
110
|
+
# Returns a copy of the data as a ChillDB::Document, with _rev set to it's
|
111
|
+
# new value on the server.
|
112
|
+
#
|
113
|
+
# Example:
|
114
|
+
# KittensApp['bigglesworth'] = {kind: 'cat', softness: 11}
|
52
115
|
def []= document, hash
|
53
116
|
raise "Not a hash?" unless hash.is_a? Hash
|
54
117
|
hash = hash.dup
|
@@ -57,31 +120,58 @@ module ChillDB
|
|
57
120
|
return ChillDB::Document.new(@@database, hash).commit!
|
58
121
|
end
|
59
122
|
|
60
|
-
#
|
61
|
-
#
|
62
|
-
|
63
|
-
|
123
|
+
# Commit an array of ChillDB::Documents and/or Hashes to the server as new or
|
124
|
+
# updated documents. This collection can include ChillDB::Document's marked
|
125
|
+
# for deletion, and is the best way to update several documents at the same
|
126
|
+
# time. All documents which can be committed will be, and any which cause
|
127
|
+
# errors will be reported via a raised ChillDB::BulkUpdateErrors.
|
128
|
+
def commit! *documents
|
129
|
+
list(documents.flatten).commit!
|
64
130
|
end
|
65
131
|
|
66
|
-
|
67
|
-
|
132
|
+
# A shortcut for #commit! which marks the documents for deletion before
|
133
|
+
# applying the commit, effectively bulk deleting them. If any deletions fail
|
134
|
+
# a ChillDB::BulkUpdateErrors will be raised with info. All deletions which
|
135
|
+
# can succeed, will.
|
136
|
+
def delete! *documents
|
137
|
+
list(documents.flatten).delete!
|
68
138
|
end
|
69
139
|
|
70
|
-
#
|
140
|
+
# creates a new ChillDB::List from an array of ChillDB::Documents and
|
141
|
+
# hashes. This method is mainly used internally for #commit! and #delete!
|
142
|
+
# You shouldn't need to use this method.
|
71
143
|
def list array = []
|
72
144
|
list = ChillDB::List.from_array array
|
73
145
|
list.database = @@database
|
74
146
|
return list
|
75
147
|
end
|
76
148
|
|
77
|
-
#
|
149
|
+
# Queries the server for every document. Returns a ChillDB::List.
|
150
|
+
#
|
151
|
+
# This method is mainly useful for maintenence and mockups. Using
|
152
|
+
# #everything in production apps is strongly discouraged, as it has severe
|
153
|
+
# scalability implications - use a ChillDB::Design view instead if you can.
|
154
|
+
#
|
155
|
+
# Example:
|
156
|
+
# # The worst way to look up a document. Never ever do this.
|
157
|
+
# all_of_them = KittensApp.everything # download all documents from server
|
158
|
+
# fredrick = all_of_them['fredrick'] # locally find just the one you want
|
159
|
+
# # Now ruby's garbage collector can happily remove every document you
|
160
|
+
# # ever made from memory. Yay!
|
78
161
|
def everything
|
79
162
|
list = ChillDB::List.load(JSON.parse(@@database.http('_all_docs?include_docs=true').get.body))
|
80
163
|
list.database = @@database
|
81
164
|
return list
|
82
165
|
end
|
83
166
|
|
84
|
-
#
|
167
|
+
# Returns this app's ChillDB::Database instance
|
168
|
+
def database
|
169
|
+
@@database
|
170
|
+
end
|
171
|
+
|
172
|
+
# Gets a reference to a resource on the database server, useful mainly
|
173
|
+
# internally. You shouldn't need to use this method unless using Couch
|
174
|
+
# features chill doesn't yet have an interface for.
|
85
175
|
def open *args
|
86
176
|
headers = { accept: '*/*' }
|
87
177
|
headers.merge! args.pop if args.last.respond_to? :to_hash
|
@@ -89,9 +179,23 @@ module ChillDB
|
|
89
179
|
end
|
90
180
|
end
|
91
181
|
|
182
|
+
|
183
|
+
# A Database abstraction full of internal gizmos and a few external ones too.
|
184
|
+
# You can access your Database via <tt>KittensApp.database</tt> (following the
|
185
|
+
# <tt>ChillDB.goes :KittensApp</tt> convention)
|
186
|
+
#
|
187
|
+
# The database object is mainly useful for maintenance. The #info method is
|
188
|
+
# neat for looking up stats on how the database is doing, and you can ask for
|
189
|
+
# a compaction, to remove old revisions and make database files smaller.
|
190
|
+
#
|
191
|
+
# ChillDB::Database is mainly used internally and isn't very useful for most
|
192
|
+
# chill apps.
|
92
193
|
class ChillDB::Database
|
93
194
|
attr_reader :url, :meta
|
94
|
-
|
195
|
+
|
196
|
+
# Initialize a new database reference. This is used internally by
|
197
|
+
# ChillDB.goes, and shouldn't be used directly
|
198
|
+
def initialize name, settings = {} # :nodoc:
|
95
199
|
@meta = {} # little place to store our things
|
96
200
|
@url = URI::HTTP.build(
|
97
201
|
host: settings[:host] || 'localhost',
|
@@ -104,14 +208,27 @@ class ChillDB::Database
|
|
104
208
|
$stderr.puts "New database created at #{@url}" if http('').put('').code == 201
|
105
209
|
end
|
106
210
|
|
107
|
-
#
|
211
|
+
# Ask the CouchDB server to compact this database, effectively making a copy
|
212
|
+
# and moving all recent revisions and data across to the new file. You can
|
213
|
+
# still keep using your app while a compact is running, and it shouldn't
|
214
|
+
# affect performance much. When using CouchDB, compacting is important as
|
215
|
+
# Couch databases don't remove any old deleted or updated documents until
|
216
|
+
# #compact! is called. This may seem a bit odd, but it is part of how couch
|
217
|
+
# can be so reliable and difficult to corrupt during power failures and
|
218
|
+
# extended server outages. Don't worry about unless your database files are
|
219
|
+
# getting too big.
|
220
|
+
#
|
221
|
+
# You can check to see if your couch server is currently compacting with
|
222
|
+
# the #info method
|
108
223
|
def compact!
|
109
224
|
request = http('_compact').post('')
|
110
225
|
raise request.body unless request.code == 202
|
111
226
|
return self
|
112
227
|
end
|
113
228
|
|
114
|
-
#
|
229
|
+
# Gets a Hash of database configuration and status info from the server
|
230
|
+
# as a ChillDB::IndifferentHash. This contains all sorts of interesting
|
231
|
+
# information useful for maintenance.
|
115
232
|
def info
|
116
233
|
response = http('').get()
|
117
234
|
IndifferentHash.new.replace(JSON.parse response.body)
|
@@ -125,8 +242,8 @@ class ChillDB::Database
|
|
125
242
|
#def revs_limit; http('_revs_limit').get.body.to_i; end
|
126
243
|
#def revs_limit=(v); http('_revs_limit').put(v.to_s); end
|
127
244
|
|
128
|
-
# grab a RestClient http resource for this database
|
129
|
-
def http resource, headers = {}
|
245
|
+
# grab a RestClient http resource for this database - useful internally
|
246
|
+
def http resource, headers = {} # :nodoc:
|
130
247
|
RestClient::Resource.new((@url + resource).to_s, headers: {accept: 'application/json', content_type: 'application/json'}.merge(headers)) { |r| r }
|
131
248
|
end
|
132
249
|
|
@@ -145,10 +262,11 @@ end
|
|
145
262
|
|
146
263
|
|
147
264
|
|
148
|
-
|
149
|
-
#
|
265
|
+
# A simple version of Hash which converts keys to strings - so symbols and
|
266
|
+
# strings can be used interchangably as keys. Works pretty much like a Hash.
|
267
|
+
# and also provides method getters and setters via #method_missing
|
150
268
|
class ChillDB::IndifferentHash < Hash
|
151
|
-
def initialize *args
|
269
|
+
def initialize *args # :nodoc:
|
152
270
|
super(*args) do |hash, key| # indifferent access
|
153
271
|
hash[key.to_s] if Symbol === key
|
154
272
|
end
|
@@ -163,25 +281,25 @@ class ChillDB::IndifferentHash < Hash
|
|
163
281
|
|
164
282
|
# make hash thing indifferent
|
165
283
|
[:merge, :merge!, :replace, :update, :update!].each do |name|
|
166
|
-
define_method name do |*args,&proc|
|
284
|
+
define_method name do |*args,&proc| # :nodoc:
|
167
285
|
super(normalize_hash(args.shift), *args, &proc)
|
168
286
|
end
|
169
287
|
end
|
170
288
|
|
171
289
|
# make hash thing indifferent
|
172
290
|
[:has_key?, :include?, :key?, :member?, :delete].each do |name|
|
173
|
-
define_method name do |first, *seconds,&proc|
|
291
|
+
define_method name do |first, *seconds,&proc| # :nodoc:
|
174
292
|
first = first.to_s if first.is_a? Symbol
|
175
293
|
super(first, *seconds, &proc)
|
176
294
|
end
|
177
295
|
end
|
178
296
|
|
179
|
-
def []= key, value
|
297
|
+
def []= key, value # :nodoc:
|
180
298
|
key = key.to_s if key.is_a? Symbol
|
181
299
|
super(key, normalize(value))
|
182
300
|
end
|
183
301
|
|
184
|
-
#
|
302
|
+
# Convert to a regular ruby Hash
|
185
303
|
def to_hash
|
186
304
|
Hash.new.replace self
|
187
305
|
end
|
@@ -212,11 +330,15 @@ end
|
|
212
330
|
|
213
331
|
|
214
332
|
|
215
|
-
|
333
|
+
# ChillDB Document, normally created from a template via ChillDB.template or
|
334
|
+
# from scratch via ChillDB.document or with a specific _id value via
|
335
|
+
# ChillDB['document-id'] or ChillDB.document('document-id'). Document
|
336
|
+
# represents a document in your database. It works as a hash with indifferent
|
337
|
+
# access and method accessors (see also ChillDB::IndifferentHash)
|
216
338
|
class ChillDB::Document < ChillDB::IndifferentHash
|
217
339
|
attr_reader :database
|
218
340
|
|
219
|
-
def initialize database, values = false
|
341
|
+
def initialize database, values = false # :nodoc:
|
220
342
|
@database = database
|
221
343
|
super()
|
222
344
|
|
@@ -227,17 +349,30 @@ class ChillDB::Document < ChillDB::IndifferentHash
|
|
227
349
|
end
|
228
350
|
end
|
229
351
|
|
352
|
+
# replace all values in this document with new ones, effectively making it
|
353
|
+
# a new document
|
354
|
+
#
|
355
|
+
# Arguments:
|
356
|
+
# values: A hash of values
|
230
357
|
def reset values
|
231
|
-
|
358
|
+
raise "Argument must be a Hash" unless values.respond_to? :to_hash
|
359
|
+
self.replace values.to_hash
|
232
360
|
self['_id'] ||= UUID.new.generate # generate an _id if we don't have one already
|
233
361
|
end
|
234
362
|
|
235
|
-
|
363
|
+
# load a documet from a ChillDB::Database, with a specific document id, and
|
364
|
+
# optionally a specific revision.
|
365
|
+
#
|
366
|
+
# Returns:
|
367
|
+
# New instance of Document
|
368
|
+
def self.load database, docid, revision = nil
|
236
369
|
new(database, _id: docid).load
|
237
370
|
end
|
238
371
|
|
239
|
-
# load
|
240
|
-
#
|
372
|
+
# load the current document again from the server, fetching any updates or
|
373
|
+
# a specific revision.
|
374
|
+
#
|
375
|
+
# Returns: self
|
241
376
|
def load lookup_revision = nil
|
242
377
|
url = URI(URI.escape self['_id'])
|
243
378
|
lookup_revision ||= self['_rev']
|
@@ -250,7 +385,11 @@ class ChillDB::Document < ChillDB::IndifferentHash
|
|
250
385
|
return self
|
251
386
|
end
|
252
387
|
|
253
|
-
#
|
388
|
+
# Write any changes to the database. If this is a new document, one will be
|
389
|
+
# created on the Couch server. If this document has a specific _id value it
|
390
|
+
# will be created or updated.
|
391
|
+
#
|
392
|
+
# Returns: self
|
254
393
|
def commit!
|
255
394
|
return delete! if self['_deleted'] # just delete if we're marked for deletion
|
256
395
|
json = JSON.generate(self)
|
@@ -263,12 +402,15 @@ class ChillDB::Document < ChillDB::IndifferentHash
|
|
263
402
|
return self
|
264
403
|
end
|
265
404
|
|
266
|
-
#
|
405
|
+
# Mark this document for deletion. A commit! is required to delete the
|
406
|
+
# document from the server. You can mark several documents for deletion and
|
407
|
+
# supply them to ChillDB.commit! for bulk deletion.
|
267
408
|
def delete
|
268
409
|
self.replace('_id'=> self['_id'], '_rev'=> self['_rev'], '_deleted'=> true)
|
269
410
|
end
|
270
411
|
|
271
|
-
# delete
|
412
|
+
# Shortcut for document.delete.commit! - deletes document from server
|
413
|
+
# immediately, returning the server's response as a Hash.
|
272
414
|
def delete!
|
273
415
|
response = @database.http(URI.escape self['_id']).delete()
|
274
416
|
response = ChillDB::IndifferentHash.new.replace JSON.parse(response.body)
|
@@ -277,26 +419,60 @@ class ChillDB::Document < ChillDB::IndifferentHash
|
|
277
419
|
return response
|
278
420
|
end
|
279
421
|
|
280
|
-
# set current revision
|
422
|
+
# set the current document revision, reloading document content from the
|
423
|
+
# couch server.
|
424
|
+
#
|
425
|
+
# Arguments:
|
426
|
+
# new_revision: (String)
|
281
427
|
def revision= new_revision
|
282
428
|
load new_revision
|
283
429
|
end
|
284
430
|
|
285
|
-
#
|
431
|
+
# get's the current document revision identifier string
|
432
|
+
#
|
433
|
+
# Returns: (String) revision identifier
|
434
|
+
def revision
|
435
|
+
self['_rev']
|
436
|
+
end
|
437
|
+
|
438
|
+
# fetch an array of this document's available revisions from the server
|
439
|
+
#
|
440
|
+
# Returns: array of revision identifier strings
|
286
441
|
def revisions
|
287
442
|
request = @database.http("#{URI.escape self['_id']}?revs=true").get
|
288
443
|
json = JSON.parse(request.body)
|
289
444
|
json['_revisions']['ids']
|
290
445
|
end
|
291
446
|
|
292
|
-
# for integration with url routers in
|
447
|
+
# to_param is supplied for integration with url routers in web frameworks
|
448
|
+
# like Rails and Camping. Defaults to returning the document's _id. You can
|
449
|
+
# just pass documents in to functions which generate urls in your views in
|
450
|
+
# many ruby frameworks, and it will be understood as the _id of the document
|
451
|
+
#
|
452
|
+
# If you have a more intelligent behaviour, monkeypatch in some special
|
453
|
+
# behaviour.
|
454
|
+
#
|
455
|
+
# Returns:
|
456
|
+
# (String): self['_id']
|
293
457
|
def to_param
|
294
458
|
self['_id']
|
295
459
|
end
|
296
460
|
|
297
|
-
# loose equality check
|
461
|
+
# A loose equality check. Two documents are considered equal if their _id
|
462
|
+
# and _rev are equal, and they are both of the same class. This is handy
|
463
|
+
# when dealing with Array's and ChillDB::List's. For instance, you may want
|
464
|
+
# to query one view, then remove the values found in another view
|
465
|
+
#
|
466
|
+
# Example:
|
467
|
+
#
|
468
|
+
# kittens = KittensApp.design(:lists).query(:cats)
|
469
|
+
# kittens -= KittensApp.design(:lists).query(:adults)
|
470
|
+
# # kittens now contains cats who are not adults
|
471
|
+
#
|
472
|
+
# Of course if you're doing this sort of thing often, you really should
|
473
|
+
# consider precomputing it in a view.
|
298
474
|
def == other_doc
|
299
|
-
other.
|
475
|
+
(other.class == self.class) and (self['_id'] == other['_id']) and (self['_rev'] == other['_rev'])
|
300
476
|
end
|
301
477
|
end
|
302
478
|
|
@@ -304,12 +480,20 @@ end
|
|
304
480
|
|
305
481
|
|
306
482
|
|
307
|
-
|
483
|
+
# A special sort of Array designed for storing lists of documents, and
|
484
|
+
# particularly the results of ChillDB::Design#query. It works sort of like a
|
485
|
+
# hash, in supporting lookups by key, and sort of like an array, in its sorted
|
486
|
+
# nature. It keeps the association of keys, documents, values, id's, and also
|
487
|
+
# provides a simple way to bulk commit all the documents in the list or delete
|
488
|
+
# all of them form the server in one efficient request.
|
489
|
+
#
|
490
|
+
# The recomended way to create a list from a regular array is ChillDB.list()
|
491
|
+
# as some of List's functionality depends on it having a link to the database.
|
308
492
|
class ChillDB::List < Array
|
309
493
|
attr_accessor :total_rows, :offset, :database
|
310
494
|
|
311
495
|
# creates a new List from a couchdb response
|
312
|
-
def self.load list, extras = {}
|
496
|
+
def self.load list, extras = {} # :nodoc:
|
313
497
|
new_list = self.new
|
314
498
|
|
315
499
|
raise "#{list['error']} - #{list['reason']}" if list['error']
|
@@ -323,7 +507,7 @@ class ChillDB::List < Array
|
|
323
507
|
end
|
324
508
|
|
325
509
|
# to make a list from a simple array (not a couchdb response...)
|
326
|
-
def self.from_array array
|
510
|
+
def self.from_array array # :nodoc:
|
327
511
|
new_list = self.new
|
328
512
|
new_list.replace array.map do |item|
|
329
513
|
{ 'id'=> item['_id'], 'key'=> item['_id'], 'value'=> item, 'doc'=> item }
|
@@ -331,7 +515,7 @@ class ChillDB::List < Array
|
|
331
515
|
end
|
332
516
|
|
333
517
|
# store rows nicely in mah belleh
|
334
|
-
def rows=(arr)
|
518
|
+
def rows=(arr) # :nodoc:
|
335
519
|
self.replace(arr.map { |item|
|
336
520
|
if item['value'].is_a? Hash
|
337
521
|
if item['value'].respond_to?(:[]) && item['value']['_id']
|
@@ -348,47 +532,81 @@ class ChillDB::List < Array
|
|
348
532
|
})
|
349
533
|
end
|
350
534
|
|
351
|
-
#
|
535
|
+
# Grab all the rows - actually just self.
|
352
536
|
def rows
|
353
537
|
self
|
354
538
|
end
|
355
539
|
|
540
|
+
# Returns an Array of all the document _id strings in the list
|
356
541
|
def ids
|
357
542
|
self.map { |i| i['id'] }
|
358
543
|
end
|
359
544
|
|
545
|
+
# Returns an Array of keys in the list
|
360
546
|
def keys
|
361
547
|
self.map { |i| i['key'] }
|
362
548
|
end
|
363
549
|
|
550
|
+
# Returns an Array of all the emitted values in the list
|
364
551
|
def values
|
365
552
|
self.map { |i| i['value'] }
|
366
553
|
end
|
367
554
|
|
555
|
+
# Returns an Array of all the documents in this list.
|
556
|
+
#
|
557
|
+
# Note that querying a view with ChillDB::Design#query does not include
|
558
|
+
# documents by default. Calling ChillDB::Design#query with the include_docs
|
559
|
+
# option set to true causes the database to lookup all of the documents.
|
368
560
|
def docs
|
369
561
|
self.map { |i| i['doc'] }
|
370
562
|
end
|
371
563
|
|
564
|
+
# Iterates each key/value pair using a supplied proc.
|
565
|
+
#
|
566
|
+
# Example:
|
567
|
+
#
|
568
|
+
# list.each_pair do |key, value|
|
569
|
+
# puts "#{key}: #{value.inspect}"
|
570
|
+
# end
|
571
|
+
#
|
572
|
+
# :yeild: key, value
|
372
573
|
def each_pair &proc
|
373
574
|
self.each { |item| proc.call(item['key'], item['value']) }
|
374
575
|
end
|
375
576
|
|
376
|
-
#
|
577
|
+
# fetch a document by it's id from this list. Useful only for queries with
|
578
|
+
# <pp>{ include_docs: true }</pp> set
|
377
579
|
def id value
|
378
580
|
self.find { |i| i['id'] == value }['doc']
|
379
581
|
end
|
380
582
|
|
583
|
+
# Lookup a key from the list. Returns a ChillDB::IndifferentHash containing:
|
584
|
+
#
|
585
|
+
# {
|
586
|
+
# 'key' => key from database
|
587
|
+
# 'value' => emitted value in view, if any
|
588
|
+
# 'doc' => document, if view queried with { include_docs: true }
|
589
|
+
# 'id' => id of document which emitted this result
|
590
|
+
# }
|
381
591
|
def key value
|
382
|
-
self.find { |i| i['key'] == value }
|
592
|
+
ChillDB::IndifferentHash.new.replace(self.find { |i| i['key'] == value })
|
383
593
|
end
|
384
594
|
|
595
|
+
# Lookup a document, by a key in the list. If there are multiple entries
|
596
|
+
# in the view with this key, the first document is returned.
|
597
|
+
#
|
598
|
+
# Note this will return nil for lists returned by ChillDB::Design#query
|
599
|
+
# unless the query was done with the include_docs option set to true.
|
385
600
|
def [] key
|
386
601
|
return key(key)['doc'] unless key.respond_to? :to_int
|
387
602
|
super
|
388
603
|
end
|
389
604
|
|
390
605
|
# make a regular ruby hash version
|
391
|
-
#
|
606
|
+
#
|
607
|
+
# By default returns a hash containing <tt>{ key: value }</tt> pairs from
|
608
|
+
# the list. Passing :doc as the optional argument instead creates a hash of
|
609
|
+
# <tt>{ key: doc }</tt> pairs.
|
392
610
|
def to_h value = :value
|
393
611
|
hash = ChillDB::IndifferentHash.new
|
394
612
|
|
@@ -399,14 +617,25 @@ class ChillDB::List < Array
|
|
399
617
|
return hash
|
400
618
|
end
|
401
619
|
|
402
|
-
#
|
403
|
-
#
|
620
|
+
# Commit every document in this list to the server in a single quick
|
621
|
+
# request. Every document which can be committed will be, and if any fail
|
622
|
+
# a ChillDB::BulkUpdateErrors will be raised.
|
623
|
+
#
|
624
|
+
# Returns self.
|
404
625
|
def commit!
|
405
626
|
commit_documents! convert
|
406
627
|
end
|
407
628
|
alias_method :commit_all!, :commit!
|
408
629
|
|
409
|
-
#
|
630
|
+
# Delete every document in this list from the server. If this list was
|
631
|
+
# returned by ChillDB::Design#query, your view's values will need to be a
|
632
|
+
# Hash containing <tt>rev</tt> or <tt>_rev</tt> - indicating the document's
|
633
|
+
# revision. You can also query your view with <tt>{ include_docs: true }</tt>
|
634
|
+
# specified as an option. Deletion cannot happen without a revision
|
635
|
+
# identifier. All documents which can be deleted, will be. If there are any
|
636
|
+
# errors, they'll be raised as a ChillDB::BulkUpdateErrors.
|
637
|
+
#
|
638
|
+
# Returns self.
|
410
639
|
def delete!
|
411
640
|
# check all the entries have a _rev - if they don't they cannot be deleted!
|
412
641
|
each do |item|
|
@@ -460,9 +689,29 @@ end
|
|
460
689
|
|
461
690
|
|
462
691
|
|
463
|
-
|
692
|
+
# Representing a named design in the Couch database, Design is used to setup
|
693
|
+
# views. Views index all of the documents in your database, creating
|
694
|
+
# ChillDB::List's for each emitted document. Your views can choose to emit
|
695
|
+
# as many entries as you like for each document, or none at all. Views can be
|
696
|
+
# as simple as listing all of a certain kind of document, or more complex
|
697
|
+
# schemes like extracting every word in a document and generating an index of
|
698
|
+
# words in documents for a simple but powerful search engine.
|
699
|
+
#
|
700
|
+
# For more information on designs and views, CouchDB: The Definitive Guide is
|
701
|
+
# a great resource. http://guide.couchdb.org/draft/design.html
|
702
|
+
#
|
703
|
+
# Example:
|
704
|
+
# KittensApp.design(:lists).views(
|
705
|
+
# # lists all cats with a softness rating of two or more
|
706
|
+
# soft_cats: 'function(doc) {
|
707
|
+
# if (doc.kind == "cat" && doc.softness > 1) emit(doc._id, null);
|
708
|
+
# }'
|
709
|
+
# ).commit!
|
710
|
+
#
|
711
|
+
# Note that views default to being javascript functions, as this is what couch
|
712
|
+
# ships with support for. It is possible to
|
464
713
|
class ChillDB::Design
|
465
|
-
attr_accessor :name
|
714
|
+
attr_accessor :name
|
466
715
|
|
467
716
|
def initialize database, name
|
468
717
|
@database = database
|
@@ -477,26 +726,107 @@ class ChillDB::Design
|
|
477
726
|
return @document
|
478
727
|
end
|
479
728
|
|
480
|
-
#
|
729
|
+
# Add more views to an existing design. See also #views
|
481
730
|
def add_views collection
|
482
731
|
document['views'].merge! views_preprocess(collection)
|
483
732
|
return self
|
484
733
|
end
|
485
734
|
|
486
|
-
#
|
735
|
+
# Set the views in this design. Argument is a Hash containing String's for
|
736
|
+
# each javascript (by default) view function. Reduce functions can also be
|
737
|
+
# included optionally.
|
738
|
+
#
|
739
|
+
# Example:
|
740
|
+
# KittensApp.design(:lists).views(
|
741
|
+
# # just a map function
|
742
|
+
# adults: 'function(doc) {
|
743
|
+
# if (doc.kind == "cat" && doc.age > 4) emit(doc._id, null);
|
744
|
+
# }',
|
745
|
+
#
|
746
|
+
# # a map and a reduce - lookup the overall softness of our kitten database
|
747
|
+
# # when queried returns just one value: the number output of the reduce
|
748
|
+
# # function.
|
749
|
+
# softness: {
|
750
|
+
# map: 'function(doc) {
|
751
|
+
# if (doc.kind == "cat") emit(doc._id, doc.softness);
|
752
|
+
# }',
|
753
|
+
# reduce: 'function(key, values, rereduce) {
|
754
|
+
# return sum(values); // add them all together
|
755
|
+
# }'
|
756
|
+
# }
|
757
|
+
# ).commit!
|
758
|
+
#
|
759
|
+
# KittensApp.design(:lists).query(:adults) #=> <ChillDB::List> [a, b, c...]
|
760
|
+
# KittensApp.design(:lists).query(:softness) #=> 19
|
761
|
+
#
|
762
|
+
# Check out http://wiki.apache.org/couchdb/Introduction_to_CouchDB_views for
|
763
|
+
# a great introduction to CouchDB view design.
|
487
764
|
def views collection
|
488
765
|
document['views'] = {}
|
489
766
|
add_views collection
|
490
767
|
return self
|
491
768
|
end
|
492
769
|
|
493
|
-
#
|
770
|
+
# get's the current language of this design document. Usually this would be
|
771
|
+
# "javascript". If called with an argument, set's the language property and
|
772
|
+
# returns self.
|
773
|
+
#
|
774
|
+
# Example:
|
775
|
+
# KittensApp.design(:lists).language #=> "javascript"
|
776
|
+
# KittensApp.design(:lists).language(:ruby) #=> KittensApp.design(:lists)
|
777
|
+
# KittensApp.design(:lists).language #=> "ruby"
|
778
|
+
#
|
779
|
+
# # chain it together with views for extra nyan:
|
780
|
+
# KittensApp.design(:lists).language(:ruby).views(
|
781
|
+
# soft_cats: %q{ proc do |doc|
|
782
|
+
# emit doc['_id'], doc['softness'] if doc['softness'] > 1
|
783
|
+
# end }
|
784
|
+
# )
|
785
|
+
#
|
786
|
+
# ChillDB doesn't currently include a ruby view server, and it needs to be
|
787
|
+
# specifically configured and installed before you can use one. More info
|
788
|
+
# in an implementation is at http://theexciter.com/articles/couchdb-views-in-ruby-instead-of-javascript.html
|
789
|
+
def language set = nil
|
790
|
+
if set
|
791
|
+
@document['language'] = set.to_s
|
792
|
+
self
|
793
|
+
else
|
794
|
+
@document['language']
|
795
|
+
end
|
796
|
+
end
|
797
|
+
|
798
|
+
# Commit this design document to the server and start the server's process
|
799
|
+
# of updating the view's contents. Note that querying the view immediately
|
800
|
+
# after a commit may be slow, while the server finishes initially processing
|
801
|
+
# it's contents. The more documents you have, the more time this can take.
|
494
802
|
def commit!
|
495
803
|
document['_id'] = "_design/#{@name}"
|
496
804
|
document.commit!
|
497
805
|
end
|
498
806
|
|
499
|
-
#
|
807
|
+
# Query a named view. Returns a ChillDB::List, which works like a Hash or an
|
808
|
+
# Array containing each result. Optionally pass in arguments. Check out
|
809
|
+
# http://wiki.apache.org/couchdb/HTTP_view_API#Querying_Options for the
|
810
|
+
# definitive list. Some really useful options:
|
811
|
+
#
|
812
|
+
# Options:
|
813
|
+
# include_docs: (true or false) load documents which emitted each entry?
|
814
|
+
# key: only return items emitted with this exact key
|
815
|
+
# keys: (Array) only return items emitted with a key in this Array
|
816
|
+
# range: (Range) shortcut for startkey, endkey, and inclusive_end
|
817
|
+
# startkey: return items starting with this key
|
818
|
+
# endkey: return items ending with this key
|
819
|
+
# startkey_docid: (String) return items starting with this document id
|
820
|
+
# endkey_docid: (String) return items ending with this document id
|
821
|
+
# inclusive_end: (true or false) defaults true, endkey included in result?
|
822
|
+
# limit: (Number) of documents to load before stopping
|
823
|
+
# stale: (String) 'ok' or 'update_after' - view need not be up to date.
|
824
|
+
# descending: (true or false) direction of search?
|
825
|
+
# skip: (Number) skip the first few documents - slow - not recomended!
|
826
|
+
# group: (true or false) should reduce grouping by exact identical key?
|
827
|
+
# group_level: (Number) first x items in key Array are used to group rows
|
828
|
+
# reduce: (true or false) should the reduce function be used?
|
829
|
+
# update_seq: (true or false) response includes sequence id of database?
|
500
830
|
def query view, options = {}
|
501
831
|
if options[:range]
|
502
832
|
range = options.delete[:range]
|
@@ -522,6 +852,7 @@ class ChillDB::Design
|
|
522
852
|
|
523
853
|
private
|
524
854
|
|
855
|
+
# handles the little shortcut for specifying only a map function
|
525
856
|
def views_preprocess views_hash
|
526
857
|
views_hash.each do |key, value|
|
527
858
|
views_hash[key] = { map: value } if value.respond_to? :to_str
|
@@ -533,19 +864,21 @@ end
|
|
533
864
|
|
534
865
|
|
535
866
|
|
536
|
-
|
867
|
+
# Represents one or more failure when doing a bulk commit or delete.
|
537
868
|
class ChillDB::BulkUpdateErrors < StandardError
|
869
|
+
# Array of failure messages
|
538
870
|
attr_accessor :failures
|
539
871
|
|
540
|
-
def initialize *args
|
872
|
+
def initialize *args # :nodoc:
|
541
873
|
@failures = args.pop
|
542
874
|
super(*args)
|
543
875
|
end
|
544
876
|
|
877
|
+
# friendly message listing the failures for each document
|
545
878
|
def inspect
|
546
|
-
"ChillDB::BulkUpdateError
|
879
|
+
"<ChillDB::BulkUpdateError>:\n" + @failures.map { |failure|
|
547
880
|
document, error = failure
|
548
|
-
" '#{document['_id']}'
|
881
|
+
" '#{document['_id']}' => #{error['reason']}"
|
549
882
|
}.join('\n')
|
550
883
|
end
|
551
884
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: chill
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '
|
4
|
+
version: '8'
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-04-
|
12
|
+
date: 2012-04-29 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: json
|
16
|
-
requirement:
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,15 @@ dependencies:
|
|
21
21
|
version: 1.0.0
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements:
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 1.0.0
|
25
30
|
- !ruby/object:Gem::Dependency
|
26
31
|
name: rest-client
|
27
|
-
requirement:
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
28
33
|
none: false
|
29
34
|
requirements:
|
30
35
|
- - ! '>='
|
@@ -32,10 +37,15 @@ dependencies:
|
|
32
37
|
version: 1.6.7
|
33
38
|
type: :runtime
|
34
39
|
prerelease: false
|
35
|
-
version_requirements:
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 1.6.7
|
36
46
|
- !ruby/object:Gem::Dependency
|
37
47
|
name: uuid
|
38
|
-
requirement:
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
39
49
|
none: false
|
40
50
|
requirements:
|
41
51
|
- - ! '>='
|
@@ -43,7 +53,12 @@ dependencies:
|
|
43
53
|
version: 2.3.4
|
44
54
|
type: :runtime
|
45
55
|
prerelease: false
|
46
|
-
version_requirements:
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 2.3.4
|
47
62
|
description: A little library to talk to a couchdb. I made it skinny, because couchdb
|
48
63
|
is very simple. I think that's a good thing.
|
49
64
|
email: a@creativepony.com
|
@@ -72,7 +87,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
72
87
|
version: '0'
|
73
88
|
requirements: []
|
74
89
|
rubyforge_project:
|
75
|
-
rubygems_version: 1.8.
|
90
|
+
rubygems_version: 1.8.21
|
76
91
|
signing_key:
|
77
92
|
specification_version: 3
|
78
93
|
summary: A tiny plug to hook ruby in to couchdb
|