chill 8 → 8.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,20 @@
1
+ require '../lib/chill'
2
+ require 'benchmark'
3
+ # A little benchmark to illustrate the performance difference between a bulk commit and individual commits
4
+ ChillDB.goes :BulkCommitBenchmark
5
+ Documents = 1000 # benchmark 1000 documents
6
+
7
+ puts "Bulk Commit of #{Documents}"
8
+ puts(bulk = Benchmark.realtime {
9
+ BulkCommitBenchmark.commit! Documents.times.map { { random_number: rand(50) } }
10
+ })
11
+
12
+ puts "Single Commit of #{Documents}"
13
+ puts(single = Benchmark.realtime {
14
+ Documents.times do
15
+ { random_number: rand(50) }
16
+ BulkCommitBenchmark.document( random_number: rand(50) ).commit!
17
+ end
18
+ })
19
+
20
+ puts "Bulk Commit was #{ ((single / bulk) * 100).round(2) }% faster!"
@@ -0,0 +1,17 @@
1
+ require '../lib/chill.rb'
2
+ # A simple test to check the new bulk commit api works as intended
3
+ # by Bluebie
4
+
5
+ ChillDB.goes :BulkBag
6
+
7
+ BulkBag.delete! BulkBag.everything # clear out our test database, using new bulk delete api
8
+
9
+ BulkBag.commit!(
10
+ { _id: 'brother1', name: "Lucky" },
11
+ { _id: 'brother2', name: "Fyr" }
12
+ );
13
+
14
+ raise "fail" unless BulkBag['brother1'].name == 'Lucky'
15
+ raise "fail" unless BulkBag['brother2'].name == 'Fyr'
16
+
17
+ puts "Success! Brother1's name is #{ BulkBag['brother1'].name }"
@@ -0,0 +1,118 @@
1
+ # Find App is a simple little fulltext search engine, to lookup documents containing words
2
+ require '../lib/chill.rb'
3
+ require 'pp'
4
+
5
+ ChillDB.goes :FindApp
6
+
7
+ # delete everything in the database so we have a nice fresh start while experimenting
8
+ FindApp.everything.delete!
9
+
10
+ # a view which will list all the words in our stories
11
+ FindApp.design(:search).views(
12
+ # words view splits up all the words then adds them as keys to this document
13
+ words: %q(
14
+ function(doc) {
15
+ if (doc.kind != "story") return;
16
+ var words = doc.text.split(/[^a-z0-9\']+/i);
17
+ for (id in words) {
18
+ emit(words[id].toLowerCase(), {_rev: doc._rev, word: words[id]})
19
+ }
20
+ }
21
+ )
22
+ ).commit!
23
+
24
+ # a little template for stories
25
+ FindApp.templates(
26
+ story: {
27
+ text: "",
28
+ name: "no name"
29
+ }
30
+ )
31
+
32
+ # add in some stories
33
+ FindApp.template(:story).merge(
34
+ name: "Charlotte and the Penguin Exhibit",
35
+ text: %(Charlotte was a computer hacker. Legendary in the scene. She was one of the few people ever to find an SSH vulnerability. Of course, being a girl she was often ignored by the other hackers, which usually worked out to her advantage. And so begins the tale of Charlotte's Big Hack. It was a sunny friday evening in antarctica, where the days are pitch black and the nights are bright as snow. Charlotte was just getting back from a whale riding expedition when she heard the familliar sound of digitized raindrops falling on sheets of glass. Someone was sending her a message!
36
+
37
+ She ran inside, throwing her big wooly coat away rushing to her computer. Dit dit dit! Each letter suspensfully appeared on her screen. "Wake up neo...". "Blargh" she announced. "This looser keeps bugging me, and I don't even know who this Neo guy is!"
38
+
39
+ After carefully typing in a sentence so masterfully cutting it best not be repeated, she sighed and flopped back on her fluffy bed with a squeak.
40
+
41
+ ...
42
+ )
43
+ ).commit!
44
+
45
+ FindApp.template(:story).merge(
46
+ name: "The origin of life",
47
+ text: "The cats said let there be life, and so there was life, and it was good."
48
+ ).commit!
49
+
50
+ FindApp.template(:story).merge(
51
+ name: "Dearest Fiona",
52
+ text: "Dearest Fiona,
53
+
54
+ Mary Pebblesworth was an unusual child. Born of parents who fled to Siam, escaping conscription in The Great War. She was never the same after returning. Insisting on eating the most horrifying of foods. Thought to be unwell from the stress of her childhood. Something unusual occurred with this patient. When placed under the care of Sir Pennyworth's Halfway House, it was noted several of her peers began craving and demanding these terrible concoctions just as she had.
55
+
56
+ At first it was thought there were some terrible joke being played, however it soon became clear we were dealing with the beginnings of a truly terrifying disease on par with childhood legends of African Zombies craving flesh and brain! In a panic Mary was brought by carriage to our estate and provided a room. We were all terribly careful of the risk of infection, and thankfully, we were fine this night.
57
+
58
+ Two weeks and three days in to my investigation, I foolishly decided on an experiment. I provided the woman with the fishes she had requested, along with exotic beans at no short expense. Carefully observing her 'cooking', so convinced of my immunity to her infectiveity, I witnessed her truly horrifying ritual. Whole fish liquefied before my very eyes, exotic beans ground to a seemingly worthless paste, then set as if by witchcraft in to what I can only describe as cheese. The smells, oh-how they defy words...
59
+
60
+ We couldn't help ourselves.
61
+
62
+ I'm terribly sorry.
63
+
64
+ Yours Faithfully,
65
+ Joseph South"
66
+ ).commit!
67
+
68
+ # brightens text in terminals which do ANSI codes
69
+ def highlight text
70
+ color_code = 7
71
+ "\e[#{color_code}m#{text}\e[0m"
72
+ end
73
+
74
+
75
+ # find a story with the word 'it' in it, or a word you specify when calling
76
+ # the program
77
+ if ARGV.empty?
78
+ print "Search: "
79
+ words = gets.split(/[^a-z0-9']+/i)
80
+ raise "No words entered" if words.empty?
81
+ else
82
+ words = ARGV
83
+ end
84
+
85
+ puts "Searching for #{ words.map { |w| highlight(w.capitalize) }.join ' ' }:\n\n"
86
+
87
+ stories = []
88
+
89
+ # lookup each word and add the stories to our collection
90
+ first_word = true
91
+ words.each do |word|
92
+ # you could make this faster by doing a single request using 'keys' instead
93
+ # of looping through each word doing a bunch of queries.
94
+ found = FindApp.design(:search).query(:words, key: word.downcase, include_docs: true)
95
+
96
+ if first_word
97
+ stories += found.docs # add all the stories we found
98
+ else
99
+ stories &= found.docs # remove stories which don't also have this word!
100
+ end
101
+
102
+ first_word = false # done being the first word now.
103
+ end
104
+
105
+ stories.uniq.each do |story|
106
+ puts "{ #{story.name.upcase} }".center(`tput cols`.to_i, '~')
107
+
108
+ text = story.text
109
+ # create a regexp which finds any of the words we're after
110
+ searcher = Regexp.new("\\b(#{words.map { |word| Regexp.escape(word)}.join('|')})\\b", 'i')
111
+ text.gsub!(searcher) { |found| highlight(found) } # underline all the search words
112
+
113
+ # underline all the search words
114
+ puts text
115
+ puts "\n\n" # blank lines
116
+ end
117
+
118
+ puts "[ SEARCH FINISHED ]".center(`tput cols`.to_i, '=')
@@ -0,0 +1,48 @@
1
+ require '../lib/chill.rb'
2
+ require 'pp'
3
+
4
+ # make a database or connect to one
5
+ ChillDB.goes :KittensApp
6
+
7
+ # delete everything and start fresh
8
+ KittensApp.everything.delete!
9
+
10
+ # add a template (just in ruby instance)
11
+ KittensApp.templates(
12
+ cat: {
13
+ color: 'invisible',
14
+ softness: 5,
15
+ likes: %w{water food flying spaceships sunlight tictacs hugs exploding},
16
+ dislikes: %w{mysql}
17
+ }
18
+ )
19
+
20
+ # add a view
21
+ KittensApp.design(:lists).views(
22
+ soft_cats: 'function(doc) {
23
+ if (doc.kind == "cat" && doc.softness > 1) emit(doc._id, null);
24
+ }'
25
+ ).commit!
26
+
27
+ # add kittens
28
+ KittensApp.template(:cat).merge(_id: 'fredrick', softness: 16, dislikes: ['silly business']).commit!
29
+ KittensApp.template(:cat).merge(_id: 'bobby', softness: 2, dislikes: ['mice']).commit!
30
+ KittensApp.template(:cat).merge(_id: 'cheezly', softness: 1, dislikes: ['soy cheese products']).commit!
31
+
32
+ # use the view to get a list of non-hard cats
33
+ puts "Kitten Database lookup - soft cats:"
34
+ soft_ones = KittensApp.design(:lists).query(:soft_cats, include_docs: true)
35
+ soft_ones.docs.each do |cat|
36
+ puts "#{cat['_id']} is #{cat['softness']} soft"
37
+ end
38
+
39
+ # just load fredrick
40
+ fredrick = KittensApp['fredrick']
41
+ puts "Fredrick's stats:"
42
+ fredrick.each do |item, value|
43
+ puts "#{item.rjust(10)}- #{value}"
44
+ end
45
+
46
+ # get the nonsoft cats the slow way - in ruby instead of with a view
47
+ nonsofties = (KittensApp.everything.docs - soft_ones.docs).select { |i| i['kind'] == 'cat' }
48
+ puts "Nonsoft cats: #{nonsofties.map { |cat| cat['_id'] }.join(', ')}"
@@ -1,7 +1,9 @@
1
1
  # Bluebie's silly little CouchDB abstraction
2
- require 'json'
3
- require 'uuid'
4
- require 'rest-client'
2
+ #
3
+ # :include:../readme.txt
4
+ require 'json' # gem dependancy
5
+ require 'rest-client' # gem dependancy
6
+ require 'securerandom'
5
7
  require 'uri'
6
8
 
7
9
  # The main ChillDB module - This is where it all starts
@@ -28,10 +30,23 @@ module ChillDB
28
30
  # # load 'frederick' from the KittensApp database
29
31
  # # on the locally installed couch server
30
32
  # KittensApp['frederick'] #=> <ChillDB::Document>
31
- def self.goes database_name, *args
33
+ #
34
+ # Options:
35
+ # user: 'couchdb_user'
36
+ # pass: 'couchdb_password'
37
+ # host: 'my-couch-server.com'
38
+ # port: 5984
39
+ # path: '/my-database/'
40
+ #
41
+ # Default options: ChillDB connects to
42
+ # http://localhost:5984/db-name-hyphenated/. When couch is freshly
43
+ # installed on a server, defaults will will work without any extra setup.
44
+ # You might want to add authentication if your server has multiple users,
45
+ # or you maybe allowing remote web access to couchdb.
46
+ def self.goes database_name, options = {}
32
47
  submod = Module.new do
33
48
  extend ChillDB
34
- @@database = ChillDB::Database.new database_name, *args
49
+ @@database = ChillDB::Database.new database_name, options
35
50
  @@templates = {}
36
51
  end
37
52
  self.constants(false).each do |const|
@@ -85,8 +100,21 @@ module ChillDB
85
100
  end
86
101
 
87
102
  # 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.
103
+ # a new blank document is created which will be assigned a fresh UUID when
104
+ # unless you specify one before committing it. You can optionally provide values
105
+ # for a new document as a hash argument. Note that documents created in this
106
+ # way are not saved to the database unless you use ChillDB::Document#commit!
107
+ # or pass them to #commit!
108
+ #
109
+ # Example:
110
+ # KittensApp['fredrick']
111
+ # #=> {"_id"=>"cheezly", "color"=>"invisible", "dislikes"=>["silly business"], ... }
112
+ # KittensApp[['fredrick', 'cheezly']]
113
+ # #=> [{"_id"=>"fredrick", ... }, {"_id"=>"cheezly", ... }]
114
+ # KittensApp[]
115
+ # #=> {}
116
+ # KittensApp[billy: 'cool', margret: 'not cool']
117
+ # #=> {"billy"=>"cool", "margret"=>"not cool", "_id"=>"df2c3d11-d50a-4db9-8f57-04fd4d511ded"}
90
118
  #
91
119
  # Returns a ChillDB::Document
92
120
  def document id = false
@@ -96,7 +124,9 @@ module ChillDB
96
124
  ChillDB::List.load(JSON.parse(response), database: @@database)
97
125
  elsif id.respond_to? :to_str
98
126
  ChillDB::Document.load(@@database, id.to_str)
99
- else
127
+ elsif id.respond_to? :to_hash
128
+ ChillDB::Document.new(@@database).reset(id)
129
+ else # just make a new blank document
100
130
  ChillDB::Document.new(@@database)
101
131
  end
102
132
  end
@@ -126,7 +156,7 @@ module ChillDB
126
156
  # time. All documents which can be committed will be, and any which cause
127
157
  # errors will be reported via a raised ChillDB::BulkUpdateErrors.
128
158
  def commit! *documents
129
- list(documents.flatten).commit!
159
+ list(documents.map { |arg| arg.respond_to?(:docs)? arg.docs : arg }.flatten(1)).commit!
130
160
  end
131
161
 
132
162
  # A shortcut for #commit! which marks the documents for deletion before
@@ -134,7 +164,7 @@ module ChillDB
134
164
  # a ChillDB::BulkUpdateErrors will be raised with info. All deletions which
135
165
  # can succeed, will.
136
166
  def delete! *documents
137
- list(documents.flatten).delete!
167
+ list(documents.map { |arg| arg.respond_to?(:docs)? arg.docs : arg }.flatten(1)).delete!
138
168
  end
139
169
 
140
170
  # creates a new ChillDB::List from an array of ChillDB::Documents and
@@ -197,11 +227,15 @@ class ChillDB::Database
197
227
  # ChillDB.goes, and shouldn't be used directly
198
228
  def initialize name, settings = {} # :nodoc:
199
229
  @meta = {} # little place to store our things
230
+ # a magical constant you can define to change the defaults for magical web hosting
231
+ # where the web server wants to configure an app's chill connection details
232
+ # dynamically without bothering the user with such things
233
+ settings = ChillDBConnectionDefaults.merge(settings) if Kernel.const_defined? :ChillDBConnectionDefaults
200
234
  @url = URI::HTTP.build(
201
235
  host: settings[:host] || 'localhost',
202
236
  port: settings[:port] || 5984,
203
237
  userinfo: [settings[:user], settings[:pass]],
204
- path: "/#{URI.escape hyphenate(name)}/"
238
+ path: settings[:path] || "/#{settings[:database_name_prefix]}#{URI.escape hyphenate(name)}/"
205
239
  )
206
240
 
207
241
  # make this database if it doesn't exist yet
@@ -357,7 +391,8 @@ class ChillDB::Document < ChillDB::IndifferentHash
357
391
  def reset values
358
392
  raise "Argument must be a Hash" unless values.respond_to? :to_hash
359
393
  self.replace values.to_hash
360
- self['_id'] ||= UUID.new.generate # generate an _id if we don't have one already
394
+ self['_id'] ||= SecureRandom.uuid # generate an _id if we don't have one already
395
+ self
361
396
  end
362
397
 
363
398
  # load a documet from a ChillDB::Database, with a specific document id, and
@@ -509,9 +544,9 @@ class ChillDB::List < Array
509
544
  # to make a list from a simple array (not a couchdb response...)
510
545
  def self.from_array array # :nodoc:
511
546
  new_list = self.new
512
- new_list.replace array.map do |item|
513
- { 'id'=> item['_id'], 'key'=> item['_id'], 'value'=> item, 'doc'=> item }
514
- end
547
+ new_list.replace(array.map { |item|
548
+ { 'id'=> item['_id'] || item[:_id], 'key'=> item['_id'] || item[:_id], 'value'=> item, 'doc'=> item }
549
+ })
515
550
  end
516
551
 
517
552
  # store rows nicely in mah belleh
@@ -653,11 +688,21 @@ class ChillDB::List < Array
653
688
  # get the list, with any non-ChillDB::Document's converted in to those
654
689
  def convert
655
690
  map do |item|
656
- document = item['doc'] || item['value']
691
+ if item['doc']
692
+ document = item['doc']
693
+ elsif item['id']
694
+ document = { _id: item['id'] }
695
+ revision = item['value']['rev'] || item['value']['_rev']
696
+ document['_rev'] ||= revision if revision
697
+ else
698
+ raise "Couldn't find _id for document in list #{item.inspect}"
699
+ end
700
+
657
701
  if document.is_a? ChillDB::Document
658
702
  document
659
703
  elsif document.respond_to? :to_hash
660
- document['_rev'] ||= item['value']['_rev'] || item['value']['rev'] || item['doc']['_rev']
704
+ revision = item['value']['_rev'] || item['value']['rev'] || item['doc']['_rev']
705
+ document['_rev'] ||= revision if revision
661
706
  document = ChillDB::Document.new(@database, document.to_hash)
662
707
  else
663
708
  raise "Cannot convert #{document.inspect}"
@@ -667,7 +712,9 @@ class ChillDB::List < Array
667
712
 
668
713
  # commit an array of documents to the server
669
714
  def commit_documents! documents
670
- response = @database.http('_bulk_docs').post(JSON.generate(docs: documents.to_a))
715
+ return if documents.empty?
716
+ body = JSON.generate(docs: documents.to_a)
717
+ response = @database.http('_bulk_docs').post(body)
671
718
  raise response.body unless (200..299).include? response.code
672
719
  json = JSON.parse(response.body)
673
720
  errors = []
@@ -675,6 +722,7 @@ class ChillDB::List < Array
675
722
  documents.each_index do |index|
676
723
  self[index]['id'] = json[index]['id']
677
724
  self[index]['value']['rev'] = json[index]['rev'] if json[index]['rev']
725
+ self[index]['doc']['_rev'] = json[index]['rev'] if json[index]['rev'] and self[index]['doc']
678
726
  errors.push [self[index], json[index]] if json[index]['error']
679
727
  end
680
728
 
@@ -0,0 +1,24 @@
1
+ Copyright (c) 2012, Jenna Fox
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are met:
6
+ * Redistributions of source code must retain the above copyright
7
+ notice, this list of conditions and the following disclaimer.
8
+ * Redistributions in binary form must reproduce the above copyright
9
+ notice, this list of conditions and the following disclaimer in the
10
+ documentation and/or other materials provided with the distribution.
11
+ * Neither the name of the chilldb nor the
12
+ names of its contributors may be used to endorse or promote products
13
+ derived from this software without specific prior written permission.
14
+
15
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18
+ DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
19
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,61 @@
1
+ .oOo. .oOo. .oOo. chill .oOo. .oOo. .oOo.
2
+
3
+ chill plugs ruby code in to CouchDB
4
+
5
+
6
+ ~~~ USAGE ~~~
7
+ require 'pp'
8
+ require 'chill'
9
+
10
+ # make a database or connect to one
11
+ ChillDB.goes :KittensApp
12
+
13
+ # add a template (just in ruby instance)
14
+ KittensApp.templates(
15
+ cat: {
16
+ color: 'invisible',
17
+ softness: 5,
18
+ likes: %w{water food flying spaceships sunlight tictacs hugs exploding},
19
+ dislikes: %w{mysql}
20
+ }
21
+ )
22
+
23
+ # get a copy of a template, change some things, and save it
24
+ KittensApp.template(:cat).merge(
25
+ color: 'green',
26
+ softness: 8,
27
+ dislikes: %w{stylesheets},
28
+ _id: 'fredrick'
29
+ )
30
+
31
+ # add a view
32
+ KittensApp.design(:lists).views(
33
+ soft_cats: 'function(doc) {
34
+ if (doc.kind == "cat" && doc.softness > 1) emit(doc._id, null);
35
+ }'
36
+ ).commit!
37
+
38
+ # add a kitten
39
+ KittensApp.template(:cat).merge(
40
+ _id: 'fredrick',
41
+ softness: 16,
42
+ dislikes: ['silly business']
43
+ ).commit!
44
+
45
+ # use the view to get a list of non-hard cats
46
+ soft_ones = KittensApp.design(:lists).query(:soft_cats)
47
+ soft_ones.each do |cat|
48
+ pp cat
49
+ end
50
+
51
+ # just load fredrick
52
+ fredrick = KittensApp['fredrick']
53
+
54
+
55
+ ~~~ MORE INFORMATION THAN YOU REQUIRE ~~~
56
+ You can see a more fully baked version of the KittensApp database in
57
+ examples/kittens-app.rb. There you will see how to do all sorts of things.
58
+ It's the start of a really great kitten database you could use to keep
59
+ track of your cats. It's web scale and cloud ready.
60
+
61
+ --- <3 Bluebie
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: '8'
4
+ version: 8.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-04-29 00:00:00.000000000 Z
12
+ date: 2012-05-03 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: json
@@ -43,22 +43,6 @@ dependencies:
43
43
  - - ! '>='
44
44
  - !ruby/object:Gem::Version
45
45
  version: 1.6.7
46
- - !ruby/object:Gem::Dependency
47
- name: uuid
48
- requirement: !ruby/object:Gem::Requirement
49
- none: false
50
- requirements:
51
- - - ! '>='
52
- - !ruby/object:Gem::Version
53
- version: 2.3.4
54
- type: :runtime
55
- prerelease: false
56
- version_requirements: !ruby/object:Gem::Requirement
57
- none: false
58
- requirements:
59
- - - ! '>='
60
- - !ruby/object:Gem::Version
61
- version: 2.3.4
62
46
  description: A little library to talk to a couchdb. I made it skinny, because couchdb
63
47
  is very simple. I think that's a good thing.
64
48
  email: a@creativepony.com
@@ -66,13 +50,21 @@ executables: []
66
50
  extensions: []
67
51
  extra_rdoc_files: []
68
52
  files:
69
- - library/chill.rb
70
- homepage: http://github.com/Bluebie/chill
53
+ - lib/chill.rb
54
+ - readme.txt
55
+ - license.txt
56
+ - examples/benchmark.rb
57
+ - examples/bulk-commit.rb
58
+ - examples/find-app.rb
59
+ - examples/kittens-app.rb
60
+ homepage: http://creativepony.com/chill/
71
61
  licenses: []
72
62
  post_install_message:
73
- rdoc_options: []
63
+ rdoc_options:
64
+ - --main
65
+ - lib/chill.rb
74
66
  require_paths:
75
- - library
67
+ - lib
76
68
  required_ruby_version: !ruby/object:Gem::Requirement
77
69
  none: false
78
70
  requirements: