chill 1

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