rufus-jig 0.1.0
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/CHANGELOG.txt +7 -0
- data/CREDITS.txt +3 -0
- data/LICENSE.txt +21 -0
- data/README.rdoc +176 -0
- data/TODO.txt +21 -0
- data/delete.txt +21 -0
- data/lib/rufus/jig/couch.rb +533 -0
- data/lib/rufus/jig/http.rb +351 -0
- data/lib/rufus/jig/json.rb +93 -0
- data/lib/rufus/jig/path.rb +99 -0
- data/lib/rufus/jig.rb +38 -0
- data/lib/rufus-jig.rb +3 -0
- data/put.txt +25 -0
- data/test/test.rb +28 -0
- metadata +70 -0
@@ -0,0 +1,533 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2009-2009, John Mettraux, jmettraux@gmail.com
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
|
+
# of this software and associated documentation files (the "Software"), to deal
|
6
|
+
# in the Software without restriction, including without limitation the rights
|
7
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
# copies of the Software, and to permit persons to whom the Software is
|
9
|
+
# furnished to do so, subject to the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be included in
|
12
|
+
# all copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
15
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
16
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
17
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
18
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
19
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
20
|
+
# THE SOFTWARE.
|
21
|
+
#
|
22
|
+
# Made in Japan.
|
23
|
+
#++
|
24
|
+
|
25
|
+
|
26
|
+
module Rufus::Jig
|
27
|
+
|
28
|
+
#
|
29
|
+
# An error class for the couch stuff.
|
30
|
+
#
|
31
|
+
# Has a #status and an #original methods.
|
32
|
+
#
|
33
|
+
class CouchError < HttpError
|
34
|
+
|
35
|
+
# the original error hash
|
36
|
+
#
|
37
|
+
attr_reader :original
|
38
|
+
|
39
|
+
def initialize (status, message)
|
40
|
+
|
41
|
+
@original = (Rufus::Jig::Json.decode(message) rescue nil) || message
|
42
|
+
|
43
|
+
if @original.is_a?(String)
|
44
|
+
super(status, @original)
|
45
|
+
else
|
46
|
+
super(status, "#{@original['error']}: #{@original['reason']}")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
#
|
52
|
+
# The parent class of Rufus::Jig::Couch CouchDatabase and CouchDocument.
|
53
|
+
#
|
54
|
+
class CouchResource
|
55
|
+
|
56
|
+
# the jig client
|
57
|
+
#
|
58
|
+
attr_reader :http
|
59
|
+
|
60
|
+
# the path for this couch resource
|
61
|
+
#
|
62
|
+
attr_reader :path
|
63
|
+
|
64
|
+
# nil for a Couch instance, the Couch instance for a CouchDatabase or
|
65
|
+
# the CouchDatabase for a CouchDocument.
|
66
|
+
#
|
67
|
+
attr_reader :parent
|
68
|
+
|
69
|
+
def initialize (parent_or_http, path)
|
70
|
+
|
71
|
+
@path = path
|
72
|
+
|
73
|
+
path = path.split('/').select { |e| e != '' }
|
74
|
+
|
75
|
+
@parent, @http = if parent_or_http.is_a?(Rufus::Jig::Http)
|
76
|
+
|
77
|
+
parent = if path.length == 0
|
78
|
+
nil
|
79
|
+
elsif path.length == 1
|
80
|
+
Couch.new(parent_or_http)
|
81
|
+
else
|
82
|
+
CouchDatabase.new(parent_or_http, path.first)
|
83
|
+
end
|
84
|
+
[ parent, parent_or_http ]
|
85
|
+
|
86
|
+
else
|
87
|
+
|
88
|
+
[ parent_or_http, parent_or_http.http ]
|
89
|
+
end
|
90
|
+
|
91
|
+
@http.options[:error_class] = CouchError
|
92
|
+
end
|
93
|
+
|
94
|
+
# Returns the Rufus::Jig::Couch instance holding this couch resource.
|
95
|
+
#
|
96
|
+
def couch
|
97
|
+
|
98
|
+
@parent == nil ? self : @parent.couch
|
99
|
+
end
|
100
|
+
|
101
|
+
# Returns the Rufus::Jig::CouchDatabase instance holding this couch
|
102
|
+
# resource (or nil if this resource is a Rufus::Jig::Couch instance).
|
103
|
+
#
|
104
|
+
def db
|
105
|
+
|
106
|
+
return nil if @parent.nil?
|
107
|
+
return self if self.is_a?(CouchDatabase)
|
108
|
+
@parent # self is a document
|
109
|
+
end
|
110
|
+
|
111
|
+
# GET, relatively to this resource.
|
112
|
+
#
|
113
|
+
def get (path, opts={})
|
114
|
+
@http.get(adjust(path), opts)
|
115
|
+
end
|
116
|
+
|
117
|
+
# POST, relatively to this resource.
|
118
|
+
#
|
119
|
+
def post (path, data, opts={})
|
120
|
+
@http.post(adjust(path), data, opts)
|
121
|
+
end
|
122
|
+
|
123
|
+
# DELETE, relatively to this resource.
|
124
|
+
#
|
125
|
+
def delete (path, opts={})
|
126
|
+
@http.delete(adjust(path), opts)
|
127
|
+
end
|
128
|
+
|
129
|
+
# PUT, relatively to this resource.
|
130
|
+
#
|
131
|
+
def put (path, data, opts={})
|
132
|
+
@http.put(adjust(path), data, opts)
|
133
|
+
end
|
134
|
+
|
135
|
+
# Returns an array of 1 or more UUIDs generated by CouchDB.
|
136
|
+
#
|
137
|
+
def get_uuids (count=1)
|
138
|
+
|
139
|
+
@http.get("/_uuids?count=#{count}")['uuids']
|
140
|
+
end
|
141
|
+
|
142
|
+
# Returns the list of all database [names] in this couch.
|
143
|
+
#
|
144
|
+
def get_databases
|
145
|
+
|
146
|
+
@http.get('/_all_dbs')
|
147
|
+
end
|
148
|
+
|
149
|
+
protected
|
150
|
+
|
151
|
+
def adjust (path)
|
152
|
+
|
153
|
+
case path
|
154
|
+
when '.' then @path
|
155
|
+
when /^\// then path
|
156
|
+
else Rufus::Jig::Path.join(@path, path)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# Fetches etag from http cache
|
161
|
+
#
|
162
|
+
def etag (path)
|
163
|
+
|
164
|
+
r = @http.cache[path]
|
165
|
+
|
166
|
+
r ? r.first : nil
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
#
|
171
|
+
# Wrapping info about a Couch server.
|
172
|
+
#
|
173
|
+
#
|
174
|
+
# Also provides a set of class methods for interacting directly with couch
|
175
|
+
# resources.
|
176
|
+
#
|
177
|
+
# * get_couch
|
178
|
+
# * get_db
|
179
|
+
# * put_db
|
180
|
+
# * delete_db
|
181
|
+
# * get_doc
|
182
|
+
# * put_doc
|
183
|
+
# * delete_doc
|
184
|
+
#
|
185
|
+
# The first one is very important
|
186
|
+
#
|
187
|
+
class Couch < CouchResource
|
188
|
+
|
189
|
+
# Never call this method directly.
|
190
|
+
#
|
191
|
+
# Do
|
192
|
+
#
|
193
|
+
# couch = Rufus::Jig::Couch.get_couch('127.0.0.1', 5984)
|
194
|
+
#
|
195
|
+
# instead.
|
196
|
+
#
|
197
|
+
def initialize (parent_or_http)
|
198
|
+
|
199
|
+
super(parent_or_http, '/')
|
200
|
+
end
|
201
|
+
|
202
|
+
# Returns a CouchDatabase instance or nil if the database doesn't
|
203
|
+
# exist in this couch.
|
204
|
+
#
|
205
|
+
# couch = Rufus::Jig::Couch.get_couch('127.0.0.1', 5984)
|
206
|
+
# db = couch.get_db('hr_documents')
|
207
|
+
#
|
208
|
+
def get_db (name)
|
209
|
+
|
210
|
+
return nil if get(name).nil?
|
211
|
+
|
212
|
+
CouchDatabase.new(couch, name)
|
213
|
+
end
|
214
|
+
|
215
|
+
# Creates a database and returns the new CouchDatabase instance.
|
216
|
+
#
|
217
|
+
# Will raise a Rufus::Jig::CouchError if the db already exists.
|
218
|
+
#
|
219
|
+
# couch = Rufus::Jig::Couch.get_couch('127.0.0.1', 5984)
|
220
|
+
# db = couch.put_db('financial_results')
|
221
|
+
#
|
222
|
+
def put_db (name)
|
223
|
+
|
224
|
+
d = CouchDatabase.new(couch, name)
|
225
|
+
d.put('.', '')
|
226
|
+
|
227
|
+
d
|
228
|
+
end
|
229
|
+
|
230
|
+
# Deletes a database, given its name.
|
231
|
+
#
|
232
|
+
# Will raise a Rufus::Jig::CouchError if the db doesn't exist.
|
233
|
+
#
|
234
|
+
# couch = Rufus::Jig::Couch.get_couch('127.0.0.1', 5984)
|
235
|
+
# db = couch.delete_db('financial_results')
|
236
|
+
#
|
237
|
+
def delete_db (name)
|
238
|
+
|
239
|
+
raise(CouchError.new(404, "no db named '#{name}'")) if get(name).nil?
|
240
|
+
|
241
|
+
delete(name)
|
242
|
+
end
|
243
|
+
|
244
|
+
#--
|
245
|
+
# handy class methods
|
246
|
+
#++
|
247
|
+
|
248
|
+
# Returns a Rufus::Jig::Couch instance.
|
249
|
+
#
|
250
|
+
# couch = Rufus::Jig::Couch.get_couch('http://127.0.0.1:5984')
|
251
|
+
# # or
|
252
|
+
# couch = Rufus::Jig::Couch.get_couch('127.0.0.1', 5984)
|
253
|
+
#
|
254
|
+
# Will raise a Rufus::Jig::CouchError in case of trouble.
|
255
|
+
#
|
256
|
+
def self.get_couch (*args)
|
257
|
+
|
258
|
+
ht, pt, pl, op = extract_http(false, *args)
|
259
|
+
|
260
|
+
Couch.new(ht)
|
261
|
+
end
|
262
|
+
|
263
|
+
# Returns a CouchDatabase instance or nil if the db doesn't exist.
|
264
|
+
#
|
265
|
+
# db = Rufus::Jig::Couch.get_db('127.0.0.1', 5984, 'my_database')
|
266
|
+
# # or
|
267
|
+
# db = Rufus::Jig::Couch.get_db('http://127.0.0.1:5984/my_database')
|
268
|
+
#
|
269
|
+
def self.get_db (*args)
|
270
|
+
|
271
|
+
ht, pt, pl, op = extract_http(false, *args)
|
272
|
+
|
273
|
+
return nil unless ht.get(pt)
|
274
|
+
|
275
|
+
CouchDatabase.new(ht, Rufus::Jig::Path.to_name(pt))
|
276
|
+
end
|
277
|
+
|
278
|
+
# Creates a database and returns a CouchDatabase instance.
|
279
|
+
#
|
280
|
+
# db = Rufus::Jig::Couch.put_db('127.0.0.1', 5984, 'my_database')
|
281
|
+
# # or
|
282
|
+
# db = Rufus::Jig::Couch.put_db('http://127.0.0.1:5984/my_database')
|
283
|
+
#
|
284
|
+
# Will raise a Rufus::Jig::CouchError if the db already exists.
|
285
|
+
#
|
286
|
+
def self.put_db (*args)
|
287
|
+
|
288
|
+
ht, pt, pl, op = extract_http(false, *args)
|
289
|
+
|
290
|
+
ht.put(pt, '')
|
291
|
+
|
292
|
+
CouchDatabase.new(ht, Rufus::Jig::Path.to_name(pt))
|
293
|
+
end
|
294
|
+
|
295
|
+
# Deletes a database.
|
296
|
+
#
|
297
|
+
# Rufus::Jig::Couch.delete_db('127.0.0.1', 5984, 'my_database')
|
298
|
+
# # or
|
299
|
+
# Rufus::Jig::Couch.delete_db('http://127.0.0.1:5984/my_database')
|
300
|
+
#
|
301
|
+
# Will raise a Rufus::Jig::CouchError if the db doesn't exist.
|
302
|
+
#
|
303
|
+
def self.delete_db (*args)
|
304
|
+
|
305
|
+
ht, pt, pl, op = extract_http(false, *args)
|
306
|
+
|
307
|
+
ht.delete(pt)
|
308
|
+
end
|
309
|
+
|
310
|
+
# Fetches a document. Returns nil if not found or a CouchDocument instance.
|
311
|
+
#
|
312
|
+
# Rufus::Jig::Couch.get_doc('127.0.0.1', 5984, 'my_database/doc0')
|
313
|
+
# # or
|
314
|
+
# Rufus::Jig::Couch.get_doc('http://127.0.0.1:5984/my_database/doc0')
|
315
|
+
#
|
316
|
+
def self.get_doc (*args)
|
317
|
+
|
318
|
+
ht, pt, pl, op = extract_http(false, *args)
|
319
|
+
|
320
|
+
doc = ht.get(pt)
|
321
|
+
|
322
|
+
doc ? CouchDocument.new(ht, pt, doc) : nil
|
323
|
+
end
|
324
|
+
|
325
|
+
# Puts (creates) a document
|
326
|
+
#
|
327
|
+
# Rufus::Jig::Couch.put_doc(
|
328
|
+
# '127.0.0.1', 5984, 'my_database/doc0', { 'a' => 'b' })
|
329
|
+
# # or
|
330
|
+
# Rufus::Jig::Couch.put_doc(
|
331
|
+
# 'http://127.0.0.1:5984/my_database/doc0', { 'x' => 'y' })
|
332
|
+
#
|
333
|
+
# To update a doc, get it first, then change its content and put it
|
334
|
+
# via its put method.
|
335
|
+
#
|
336
|
+
def self.put_doc (*args)
|
337
|
+
|
338
|
+
ht, pt, pl, op = extract_http(true, *args)
|
339
|
+
|
340
|
+
info = ht.put(pt, pl, :content_type => :json, :cache => false)
|
341
|
+
|
342
|
+
CouchDocument.new(ht, pt, Rufus::Jig.marshal_copy(pl), info)
|
343
|
+
end
|
344
|
+
|
345
|
+
# Deletes a document.
|
346
|
+
#
|
347
|
+
# Rufus::Jig::Couch.delete_doc('127.0.0.1', 5984, 'my_database/doc0')
|
348
|
+
# # or
|
349
|
+
# Rufus::Jig::Couch.delete_doc('http://127.0.0.1:5984/my_database/doc0')
|
350
|
+
#
|
351
|
+
# Will raise a Rufus::Jig::CouchError if the doc doesn't exist.
|
352
|
+
#
|
353
|
+
def self.delete_doc (*args)
|
354
|
+
|
355
|
+
ht, pt, pl, op = extract_http(false, *args)
|
356
|
+
|
357
|
+
ht.delete(pt)
|
358
|
+
end
|
359
|
+
|
360
|
+
# This method is used from get_couch, get_db, put_db and co...
|
361
|
+
#
|
362
|
+
# Never used directly.
|
363
|
+
#
|
364
|
+
def self.extract_http (payload_expected, *args)
|
365
|
+
|
366
|
+
a = Rufus::Jig::Http.extract_http(payload_expected, *args)
|
367
|
+
|
368
|
+
a.first.error_class = Rufus::Jig::CouchError
|
369
|
+
|
370
|
+
a
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
374
|
+
#
|
375
|
+
# Wrapping info about a Couch database.
|
376
|
+
#
|
377
|
+
# You usually grab an instance of it like that :
|
378
|
+
#
|
379
|
+
# db = Rufus::Jig::Couch.get_db('127.0.0.1', 5984, 'my_database')
|
380
|
+
# # or
|
381
|
+
# db = Rufus::Jig::Couch.get_db('http://127.0.0.1:5984/my_database')
|
382
|
+
#
|
383
|
+
# # or
|
384
|
+
# couch = Rufus::Jig::Couch.get_couch('127.0.0.1', 5984)
|
385
|
+
# db = Rufus::Jig::Couch.get_db('my_database')
|
386
|
+
#
|
387
|
+
class CouchDatabase < CouchResource
|
388
|
+
|
389
|
+
attr_reader :name
|
390
|
+
|
391
|
+
# Usually called via Couch#get_database(name)
|
392
|
+
#
|
393
|
+
def initialize (parent_or_http, name)
|
394
|
+
|
395
|
+
@name = name
|
396
|
+
|
397
|
+
super(parent_or_http, Rufus::Jig::Path.to_path(@name))
|
398
|
+
end
|
399
|
+
|
400
|
+
# Given an id and an JSONable hash, puts the doc to the database
|
401
|
+
# and returns a CouchDocument instance wrapping it.
|
402
|
+
#
|
403
|
+
# db.put_doc('doc0', { 'item' => 'car', 'brand' => 'bmw' })
|
404
|
+
#
|
405
|
+
def put_doc (doc_id, doc)
|
406
|
+
|
407
|
+
info = put(doc_id, doc, :content_type => :json, :cache => false)
|
408
|
+
|
409
|
+
CouchDocument.new(
|
410
|
+
self,
|
411
|
+
Rufus::Jig::Path.join(@name, doc_id),
|
412
|
+
Rufus::Jig.marshal_copy(doc), info)
|
413
|
+
end
|
414
|
+
|
415
|
+
# Gets a document, given its id.
|
416
|
+
# (conditional GET).
|
417
|
+
#
|
418
|
+
# db.get_doc('doc0')
|
419
|
+
#
|
420
|
+
def get_doc (doc_id)
|
421
|
+
|
422
|
+
path = Rufus::Jig::Path.join(@path, doc_id)
|
423
|
+
opts = {}
|
424
|
+
|
425
|
+
if et = etag(path)
|
426
|
+
opts[:etag] = et
|
427
|
+
end
|
428
|
+
|
429
|
+
doc = get(path, opts)
|
430
|
+
|
431
|
+
doc ?
|
432
|
+
CouchDocument.new(self, Rufus::Jig::Path.join(@name, doc_id), doc) :
|
433
|
+
nil
|
434
|
+
end
|
435
|
+
|
436
|
+
# Deletes a document, you have to provide the current revision.
|
437
|
+
#
|
438
|
+
# db.delete_doc('doc0')
|
439
|
+
#
|
440
|
+
def delete_doc (doc_id, rev)
|
441
|
+
|
442
|
+
raise(ArgumentError.new("no doc '#{name}'")) if get(doc_id).nil?
|
443
|
+
|
444
|
+
delete(Rufus::Jig::Path.add_params(doc_id, :rev => rev))
|
445
|
+
end
|
446
|
+
end
|
447
|
+
|
448
|
+
#
|
449
|
+
# Wrapping a couch document.
|
450
|
+
#
|
451
|
+
# Responds to [] and []=
|
452
|
+
#
|
453
|
+
class CouchDocument < CouchResource
|
454
|
+
|
455
|
+
attr_reader :payload
|
456
|
+
|
457
|
+
# Don't call this method directly, use one of the get_doc or put_doc
|
458
|
+
# methods.
|
459
|
+
#
|
460
|
+
def initialize (parent_or_http, path, doc, put_result=nil)
|
461
|
+
|
462
|
+
super(parent_or_http, path)
|
463
|
+
@payload = doc
|
464
|
+
|
465
|
+
if put_result
|
466
|
+
@payload['_id'] = put_result['id']
|
467
|
+
@payload['_rev'] = put_result['rev']
|
468
|
+
end
|
469
|
+
end
|
470
|
+
|
471
|
+
# Gets a value.
|
472
|
+
#
|
473
|
+
def [] (k)
|
474
|
+
@payload[k]
|
475
|
+
end
|
476
|
+
|
477
|
+
# Sets a value.
|
478
|
+
#
|
479
|
+
def []= (k, v)
|
480
|
+
@payload[k] = v
|
481
|
+
end
|
482
|
+
|
483
|
+
# Returns to CouchDB id of the document.
|
484
|
+
#
|
485
|
+
def _id
|
486
|
+
@payload['_id']
|
487
|
+
end
|
488
|
+
|
489
|
+
# Returns the revision string for this copy of the document.
|
490
|
+
#
|
491
|
+
def _rev
|
492
|
+
@payload['_rev']
|
493
|
+
end
|
494
|
+
|
495
|
+
# Re-gets this document (updating its _rev and content if necessary).
|
496
|
+
#
|
497
|
+
def get
|
498
|
+
|
499
|
+
opts = {}
|
500
|
+
|
501
|
+
if @payload && rev = @payload['_rev']
|
502
|
+
opts[:etag] = "\"#{rev}\""
|
503
|
+
end
|
504
|
+
|
505
|
+
h = super(@path, opts)
|
506
|
+
|
507
|
+
raise(CouchError.new(410, 'probably gone')) unless h
|
508
|
+
|
509
|
+
@payload = h
|
510
|
+
|
511
|
+
self
|
512
|
+
end
|
513
|
+
|
514
|
+
# Deletes this document (from Couch).
|
515
|
+
#
|
516
|
+
def delete
|
517
|
+
|
518
|
+
super(Rufus::Jig::Path.add_params(@path, :rev => _rev))
|
519
|
+
end
|
520
|
+
|
521
|
+
# Puts this document (assumes you have updated it).
|
522
|
+
#
|
523
|
+
def put
|
524
|
+
|
525
|
+
h = super(
|
526
|
+
@path, @payload,
|
527
|
+
:content_type => :json, :etag => "\"#{@payload['_rev']}\"")
|
528
|
+
|
529
|
+
@payload['_rev'] = h['rev']
|
530
|
+
end
|
531
|
+
end
|
532
|
+
end
|
533
|
+
|