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.
Files changed (2) hide show
  1. data/library/chill.rb +379 -0
  2. 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: []