chill 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/library/chill.rb +379 -0
- metadata +79 -0
data/library/chill.rb
ADDED
@@ -0,0 +1,379 @@
|
|
1
|
+
# Bluebie's silly little CouchDB abstraction
|
2
|
+
require 'json'
|
3
|
+
require 'uuid'
|
4
|
+
require 'rest-client'
|
5
|
+
require 'uri'
|
6
|
+
|
7
|
+
module ChillDB
|
8
|
+
def self.goes database_name, *args
|
9
|
+
submod = Module.new do
|
10
|
+
extend ChillDB
|
11
|
+
@@database = ChillDB::Database.new database_name, *args
|
12
|
+
@@templates = {}
|
13
|
+
end
|
14
|
+
self.constants(false).each do |const|
|
15
|
+
submod.const_set(const, self.const_get(const));
|
16
|
+
end
|
17
|
+
Object.const_set(database_name, submod)
|
18
|
+
end
|
19
|
+
|
20
|
+
# stores a list of templates which can be used to make a new document
|
21
|
+
def templates obj
|
22
|
+
@@templates.merge!(obj) if obj and obj.is_a? Hash
|
23
|
+
return @@templates
|
24
|
+
end
|
25
|
+
|
26
|
+
# get a new document consisting of a template
|
27
|
+
def template kind
|
28
|
+
properties = @@templates[kind].dup
|
29
|
+
properties[:kind] = kind.to_s
|
30
|
+
ChillDB::Document.new(@@database, properties)
|
31
|
+
end
|
32
|
+
|
33
|
+
# get a design with a particular name - or make one!
|
34
|
+
def design name
|
35
|
+
ChillDB::Design.new @@database, name
|
36
|
+
end
|
37
|
+
|
38
|
+
# get or make a document with a particular id/name, or just a blank new one
|
39
|
+
def document id = false
|
40
|
+
if id
|
41
|
+
ChillDB::Document.load(@@database, id)
|
42
|
+
else
|
43
|
+
ChillDB::Document.new(@@database)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
alias_method :[], :document
|
47
|
+
|
48
|
+
def []= document, hash
|
49
|
+
raise "Not a hash?" unless hash.is_a? Hash
|
50
|
+
hash = hash.dup
|
51
|
+
hash['_id'] = document
|
52
|
+
return hash.commit! if hash.is_a? ChillDB::Document
|
53
|
+
return ChillDB::Document.new(@@database, hash).commit!
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class ChillDB::Database
|
58
|
+
attr_reader :url, :meta
|
59
|
+
def initialize name, settings = {}
|
60
|
+
@meta = {} # little place to store our things
|
61
|
+
@url = URI::HTTP.build(
|
62
|
+
host: settings[:host] || 'localhost',
|
63
|
+
port: settings[:port] || 5984,
|
64
|
+
userinfo: [settings[:user], settings[:pass]],
|
65
|
+
path: "/#{URI.escape hyphenate(name)}/"
|
66
|
+
)
|
67
|
+
|
68
|
+
# make this database if it doesn't exist yet
|
69
|
+
$stderr.puts "New database created at #{@url}" if http('').put('').code == 201
|
70
|
+
end
|
71
|
+
|
72
|
+
# ask the server to compact this database
|
73
|
+
def compact!
|
74
|
+
request = http('_compact').post('')
|
75
|
+
raise request.body unless request.code == 202
|
76
|
+
return self
|
77
|
+
end
|
78
|
+
|
79
|
+
#def revs_limit; http('_revs_limit').get.body.to_i; end
|
80
|
+
#def revs_limit=(v); http('_revs_limit').put(v.to_s); end
|
81
|
+
|
82
|
+
# grab a RestClient http resource for this database
|
83
|
+
def http resource
|
84
|
+
RestClient::Resource.new((@url + resource).to_s, :headers => {accept: 'application/json', content_type: 'application/json'}) { |r| r }
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
# a little utility to hyphenate a string
|
90
|
+
def hyphenate string
|
91
|
+
string.to_s.gsub(/(.)([A-Z])/, '\1-\2').downcase
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
|
97
|
+
|
98
|
+
|
99
|
+
|
100
|
+
|
101
|
+
|
102
|
+
|
103
|
+
# handles conversion of symbol keys to strings, and method accessors
|
104
|
+
class ChillDB::IndifferentHash < Hash
|
105
|
+
def initialize *args
|
106
|
+
super(*args) do |hash, key| # indifferent access
|
107
|
+
hash[key.to_s] if Symbol === key
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# getters and setters for hash items
|
112
|
+
def method_missing name, *args
|
113
|
+
return self[name.to_s] if self[name.to_s]
|
114
|
+
return self[name.to_s[0...-1]] = args.first if name.to_s.end_with? '=' and args.length == 1
|
115
|
+
super
|
116
|
+
end
|
117
|
+
|
118
|
+
# make hash thing indifferent
|
119
|
+
[:merge, :merge!, :replace, :update, :update!].each do |name|
|
120
|
+
define_method name do |*args,&proc|
|
121
|
+
super(normalize_hash(args.shift), *args, &proc)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# make hash thing indifferent
|
126
|
+
[:has_key?, :include?, :key?, :member?, :delete].each do |name|
|
127
|
+
define_method name do |first, *seconds,&proc|
|
128
|
+
first = first.to_s if first.is_a? Symbol
|
129
|
+
super(first, *seconds, &proc)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def []= key, value
|
134
|
+
key = key.to_s if key.is_a? Symbol
|
135
|
+
super(key, normalize(value))
|
136
|
+
end
|
137
|
+
|
138
|
+
# return an actual hash
|
139
|
+
def to_hash
|
140
|
+
Hash.new.replace self
|
141
|
+
end
|
142
|
+
|
143
|
+
|
144
|
+
private
|
145
|
+
# normalises all symbols in a hash in to string keys
|
146
|
+
def normalize_hash original_hash
|
147
|
+
hash = {}
|
148
|
+
original_hash.each do |key,value|
|
149
|
+
key = key.to_s if key.is_a? Symbol
|
150
|
+
hash[key] = normalize value
|
151
|
+
end
|
152
|
+
return hash
|
153
|
+
end
|
154
|
+
|
155
|
+
def normalize thing
|
156
|
+
return ChillDB::IndifferentHash.new.replace(thing) if thing.is_a? Hash
|
157
|
+
return thing.map { |i| normalize(i) } if thing.is_a? Array
|
158
|
+
return thing;
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
|
163
|
+
|
164
|
+
|
165
|
+
|
166
|
+
|
167
|
+
|
168
|
+
|
169
|
+
|
170
|
+
class ChillDB::Document < ChillDB::IndifferentHash
|
171
|
+
attr_reader :database
|
172
|
+
|
173
|
+
def initialize database, values = false
|
174
|
+
@database = database
|
175
|
+
super()
|
176
|
+
|
177
|
+
if values.is_a? Symbol
|
178
|
+
reset @database.class_variable_get(:@@templates)[values]
|
179
|
+
elsif values
|
180
|
+
reset values
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def reset values
|
185
|
+
self.replace values
|
186
|
+
self['_id'] ||= UUID.new.generate # generate an _id if we don't have one already
|
187
|
+
end
|
188
|
+
|
189
|
+
def self.load database, docid
|
190
|
+
new(database, _id: docid).load
|
191
|
+
end
|
192
|
+
|
193
|
+
# load this document from server in to this local cache
|
194
|
+
# returns self, or if there's a more specific subclass, a subclassed version of Document
|
195
|
+
def load lookup_revision = nil
|
196
|
+
url = URI(URI.escape self['_id'])
|
197
|
+
url.query = "rev=#{lookup_revision}" if lookup_revision
|
198
|
+
response = @database.http(url).get
|
199
|
+
return self if response.code == 404 # we can create it later
|
200
|
+
raise response.body if response.code != 200
|
201
|
+
|
202
|
+
reset JSON.parse(response.body)
|
203
|
+
return self
|
204
|
+
end
|
205
|
+
|
206
|
+
# stores updates (or newly existingness) of document to origin database
|
207
|
+
def commit!
|
208
|
+
json = JSON.generate(self)
|
209
|
+
response = @database.http(URI.escape self['_id']).put(json);
|
210
|
+
raise response.body unless (200..299).include? response.code
|
211
|
+
json = JSON.parse(response.body)
|
212
|
+
raise "Not ok! #{response.body}" unless json['ok']
|
213
|
+
self['_id'] = json['id']
|
214
|
+
self['_rev'] = json['rev']
|
215
|
+
return self
|
216
|
+
end
|
217
|
+
|
218
|
+
# delete this jerk
|
219
|
+
def delete!
|
220
|
+
response = @database.http(URI.escape self['_id']).delete()
|
221
|
+
response = ChillDB::IndifferentHash.new.replace JSON.parse(response.body)
|
222
|
+
raise "Couldn't delete #{self._id}: #{response.error} - #{response.reason}" if response['error']
|
223
|
+
return response
|
224
|
+
end
|
225
|
+
|
226
|
+
# set current revision to a different thing
|
227
|
+
def revision= new_revision
|
228
|
+
load new_revision
|
229
|
+
end
|
230
|
+
|
231
|
+
# gets a list of revisions
|
232
|
+
def revisions
|
233
|
+
request = @database.http("#{URI.escape self['_id']}?revs=true").get
|
234
|
+
json = JSON.parse(request.body)
|
235
|
+
json['_revisions']['ids']
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
|
240
|
+
|
241
|
+
|
242
|
+
|
243
|
+
|
244
|
+
class ChillDB::List < Array
|
245
|
+
attr_accessor :total_rows, :offset, :database
|
246
|
+
|
247
|
+
# store rows nicely in mah belleh
|
248
|
+
def rows=(arr)
|
249
|
+
self.replace arr.map { |item| ChillDB::IndifferentHash.new.replace(item) }
|
250
|
+
end
|
251
|
+
|
252
|
+
# we are the rows!
|
253
|
+
def rows
|
254
|
+
self
|
255
|
+
end
|
256
|
+
|
257
|
+
def ids
|
258
|
+
self.map { |i| i['id'] }
|
259
|
+
end
|
260
|
+
|
261
|
+
def keys
|
262
|
+
self.map { |i| i['key'] }
|
263
|
+
end
|
264
|
+
|
265
|
+
def values
|
266
|
+
self.map { |i| i['value'] }
|
267
|
+
end
|
268
|
+
|
269
|
+
def each_pair &proc
|
270
|
+
self.each { |item| proc.call(item['key'], item['value']) }
|
271
|
+
end
|
272
|
+
|
273
|
+
# make a regular ruby hash version
|
274
|
+
def to_h
|
275
|
+
hash = ChillDB::IndifferentHash.new
|
276
|
+
|
277
|
+
each do |item|
|
278
|
+
hash[item['key']] = item['value']
|
279
|
+
end
|
280
|
+
|
281
|
+
return hash
|
282
|
+
end
|
283
|
+
alias_method :to_hash, :to_h
|
284
|
+
|
285
|
+
# remove all the documents in this list from the database
|
286
|
+
def delete_all!
|
287
|
+
each { |item|
|
288
|
+
raise "Not all documents listed are emitted as values in this view" if item.id != item.value._id
|
289
|
+
}
|
290
|
+
|
291
|
+
request = { docs: map { |item|
|
292
|
+
{ _id: item.value._id, _rev: item.value._rev, _deleted: true }
|
293
|
+
} }
|
294
|
+
|
295
|
+
response = JSON.parse @database.http("_bulk_docs").post(request.to_json)
|
296
|
+
raise "Error: #{response['error']} - #{response['reason']}" if response.is_a? Hash and response['error']
|
297
|
+
|
298
|
+
return ChillDB::IndifferentHash.new.replace response
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
|
303
|
+
|
304
|
+
|
305
|
+
|
306
|
+
|
307
|
+
|
308
|
+
class ChillDB::Design
|
309
|
+
attr_accessor :name # can rename with that - though leaves old copy on server probably
|
310
|
+
|
311
|
+
def initialize database, name
|
312
|
+
@database = database
|
313
|
+
@name = name.to_s
|
314
|
+
end
|
315
|
+
|
316
|
+
# lazy load document - referencing this causes load from database
|
317
|
+
def document
|
318
|
+
@document ||= ChillDB::Document::load @database, "_design/#{@name}"
|
319
|
+
@document['language'] ||= 'javascript'
|
320
|
+
@document['views'] ||= {}
|
321
|
+
return @document
|
322
|
+
end
|
323
|
+
|
324
|
+
# adds views
|
325
|
+
def add_views collection
|
326
|
+
document['views'].merge! collection
|
327
|
+
return self
|
328
|
+
end
|
329
|
+
|
330
|
+
# sets views
|
331
|
+
def views collection
|
332
|
+
document['views'] = {}
|
333
|
+
add_views collection
|
334
|
+
return self
|
335
|
+
end
|
336
|
+
|
337
|
+
# store changes to server
|
338
|
+
def commit!
|
339
|
+
document['_id'] = "_design/#{@name}"
|
340
|
+
document.commit!
|
341
|
+
end
|
342
|
+
|
343
|
+
# query a view - response is an array augmented with methods like arr.total_rows
|
344
|
+
def query view, options = {}
|
345
|
+
if options[:range]
|
346
|
+
range = options.delete[:range]
|
347
|
+
options[:startkey], options[:endkey] = range.first, range.last
|
348
|
+
options[:startkey], options[:endkey] = options[:endkey], options[:startkey] if options[:descending]
|
349
|
+
options[:inclusive_end] = !range.exclude_end?
|
350
|
+
end
|
351
|
+
|
352
|
+
# these options need to be json encoded
|
353
|
+
[:key, :startkey, :endkey, :keys, :descending, :limit, :skip, :group, :group_level,
|
354
|
+
:reduce, :include_docs, :inclusive_end, :update_seq
|
355
|
+
].each do |name|
|
356
|
+
options[name] = options[name].to_json if options.has_key? name
|
357
|
+
end
|
358
|
+
|
359
|
+
opts = options.map { |key, value| "#{URI.escape(key.to_s)}=#{URI.escape(value.to_s)}" }.join('&')
|
360
|
+
url = "_design/#{URI.escape @name}/_view/#{URI.escape view.to_s}?#{opts}"
|
361
|
+
response = @database.http(url).get()
|
362
|
+
json = JSON.parse response.body
|
363
|
+
raise "#{json['error']} - #{json['reason']} @ #{url}" if json['error']
|
364
|
+
|
365
|
+
# put the results in to a QueryResults object - a glorified array
|
366
|
+
results = ChillDB::List.new
|
367
|
+
results.database = @database
|
368
|
+
json.each do |key, value|
|
369
|
+
results.send("#{key}=", value)
|
370
|
+
end
|
371
|
+
|
372
|
+
return results
|
373
|
+
end
|
374
|
+
|
375
|
+
end
|
376
|
+
|
377
|
+
|
378
|
+
|
379
|
+
|
metadata
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: chill
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '1'
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Bluebie
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-10-01 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: json
|
16
|
+
requirement: &70155679871420 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 1.6.0
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70155679871420
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rest-client
|
27
|
+
requirement: &70155679870940 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 1.6.7
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70155679870940
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: uuid
|
38
|
+
requirement: &70155679870480 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 2.3.4
|
44
|
+
type: :runtime
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70155679870480
|
47
|
+
description: A little library to talk to a couchdb. I made it skinny, because couchdb
|
48
|
+
is very simple. I think that's a good thing.
|
49
|
+
email: a@creativepony.com
|
50
|
+
executables: []
|
51
|
+
extensions: []
|
52
|
+
extra_rdoc_files: []
|
53
|
+
files:
|
54
|
+
- library/chill.rb
|
55
|
+
homepage: http://github.com/Bluebie/chill
|
56
|
+
licenses: []
|
57
|
+
post_install_message:
|
58
|
+
rdoc_options: []
|
59
|
+
require_paths:
|
60
|
+
- .
|
61
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
62
|
+
none: false
|
63
|
+
requirements:
|
64
|
+
- - ! '>='
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '0'
|
67
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
68
|
+
none: false
|
69
|
+
requirements:
|
70
|
+
- - ! '>='
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: '0'
|
73
|
+
requirements: []
|
74
|
+
rubyforge_project:
|
75
|
+
rubygems_version: 1.8.5
|
76
|
+
signing_key:
|
77
|
+
specification_version: 3
|
78
|
+
summary: A tiny plug to hook ruby in to couchdb
|
79
|
+
test_files: []
|