dpla-couchrest 1.2.1.pre.dpla

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +7 -0
  3. data/.travis.yml +8 -0
  4. data/Gemfile +2 -0
  5. data/LICENSE +176 -0
  6. data/README.md +66 -0
  7. data/Rakefile +23 -0
  8. data/THANKS.md +21 -0
  9. data/VERSION +1 -0
  10. data/couchrest.gemspec +36 -0
  11. data/examples/word_count/markov +38 -0
  12. data/examples/word_count/views/books/chunked-map.js +3 -0
  13. data/examples/word_count/views/books/united-map.js +1 -0
  14. data/examples/word_count/views/markov/chain-map.js +6 -0
  15. data/examples/word_count/views/markov/chain-reduce.js +7 -0
  16. data/examples/word_count/views/word_count/count-map.js +6 -0
  17. data/examples/word_count/views/word_count/count-reduce.js +3 -0
  18. data/examples/word_count/word_count.rb +46 -0
  19. data/examples/word_count/word_count_query.rb +40 -0
  20. data/examples/word_count/word_count_views.rb +26 -0
  21. data/history.txt +214 -0
  22. data/init.rb +1 -0
  23. data/lib/couchrest.rb +146 -0
  24. data/lib/couchrest/attributes.rb +89 -0
  25. data/lib/couchrest/commands/generate.rb +71 -0
  26. data/lib/couchrest/commands/push.rb +103 -0
  27. data/lib/couchrest/database.rb +402 -0
  28. data/lib/couchrest/design.rb +91 -0
  29. data/lib/couchrest/document.rb +105 -0
  30. data/lib/couchrest/helper/attachments.rb +29 -0
  31. data/lib/couchrest/helper/pager.rb +103 -0
  32. data/lib/couchrest/helper/streamer.rb +60 -0
  33. data/lib/couchrest/helper/upgrade.rb +51 -0
  34. data/lib/couchrest/middlewares/logger.rb +263 -0
  35. data/lib/couchrest/monkeypatches.rb +25 -0
  36. data/lib/couchrest/rest_api.rb +166 -0
  37. data/lib/couchrest/server.rb +92 -0
  38. data/lib/couchrest/support/inheritable_attributes.rb +107 -0
  39. data/spec/.gitignore +1 -0
  40. data/spec/couchrest/couchrest_spec.rb +197 -0
  41. data/spec/couchrest/database_spec.rb +914 -0
  42. data/spec/couchrest/design_spec.rb +206 -0
  43. data/spec/couchrest/document_spec.rb +400 -0
  44. data/spec/couchrest/helpers/pager_spec.rb +115 -0
  45. data/spec/couchrest/helpers/streamer_spec.rb +134 -0
  46. data/spec/couchrest/rest_api_spec.rb +241 -0
  47. data/spec/couchrest/server_spec.rb +35 -0
  48. data/spec/fixtures/attachments/README +3 -0
  49. data/spec/fixtures/attachments/couchdb.png +0 -0
  50. data/spec/fixtures/attachments/test.html +11 -0
  51. data/spec/fixtures/views/lib.js +3 -0
  52. data/spec/fixtures/views/test_view/lib.js +3 -0
  53. data/spec/fixtures/views/test_view/only-map.js +4 -0
  54. data/spec/fixtures/views/test_view/test-map.js +3 -0
  55. data/spec/fixtures/views/test_view/test-reduce.js +3 -0
  56. data/spec/spec.opts +5 -0
  57. data/spec/spec_helper.rb +46 -0
  58. data/utils/remap.rb +27 -0
  59. data/utils/subset.rb +30 -0
  60. metadata +212 -0
@@ -0,0 +1,91 @@
1
+ module CouchRest
2
+ class Design < Document
3
+
4
+ def view_by *keys
5
+ opts = keys.pop if keys.last.is_a?(Hash)
6
+ opts ||= {}
7
+ self['views'] ||= {}
8
+ method_name = "by_#{keys.join('_and_')}"
9
+
10
+ if opts[:map]
11
+ view = {}
12
+ view['map'] = opts.delete(:map)
13
+ view['reduce'] = opts.delete(:reduce) if opts[:reduce]
14
+ self['views'][method_name] = view
15
+ else
16
+ doc_keys = keys.collect{|k| "doc['#{k}']"}
17
+ key_emit = doc_keys.length == 1 ? "#{doc_keys.first}" : "[#{doc_keys.join(', ')}]"
18
+ guards = opts.delete(:guards) || []
19
+ guards += doc_keys.map{|k| "(#{k} != null)"} unless opts.delete(:allow_nil)
20
+ guards << 'true' if guards.empty?
21
+ map_function = <<-JAVASCRIPT
22
+ function(doc) {
23
+ if (#{guards.join(' && ')}) {
24
+ emit(#{key_emit}, null);
25
+ }
26
+ }
27
+ JAVASCRIPT
28
+ self['views'][method_name] = {
29
+ 'map' => map_function
30
+ }
31
+ end
32
+ self['views'][method_name]['couchrest-defaults'] = opts unless opts.empty?
33
+ method_name
34
+ end
35
+
36
+ # Dispatches to any named view.
37
+ # (using the database where this design doc was saved)
38
+ def view view_name, query={}, &block
39
+ view_on database, view_name, query, &block
40
+ end
41
+
42
+ # Dispatches to any named view in a specific database
43
+ def view_on db, view_name, query = {}, &block
44
+ raise ArgumentError, "View query options must be set as symbols!" if query.keys.find{|k| k.is_a?(String)}
45
+ view_name = view_name.to_s
46
+ view_slug = "#{name}/#{view_name}"
47
+ # Set the default query options
48
+ query = view_defaults(view_name).merge(query)
49
+ # Ensure reduce is set if dealing with a reduceable view
50
+ # This is a requirement of CouchDB.
51
+ query[:reduce] ||= false if can_reduce_view?(view_name)
52
+ db.view(view_slug, query, &block)
53
+ end
54
+
55
+ def name
56
+ id.sub('_design/','') if id
57
+ end
58
+
59
+ def name= newname
60
+ self['_id'] = "_design/#{newname}"
61
+ end
62
+
63
+ def save
64
+ raise ArgumentError, "_design docs require a name" unless name && name.length > 0
65
+ super
66
+ end
67
+
68
+ # Return the hash of default values to include in all queries sent
69
+ # to a view from couchrest.
70
+ def view_defaults(name)
71
+ (self['views'][name.to_s] && self['views'][name.to_s]["couchrest-defaults"]) || {}
72
+ end
73
+
74
+ # Returns true or false if the view is available.
75
+ def has_view?(name)
76
+ !self['views'][name.to_s].nil?
77
+ end
78
+
79
+ # Check if the view has a reduce method defined.
80
+ def can_reduce_view?(name)
81
+ has_view?(name) && !self['views'][name.to_s]['reduce'].to_s.empty?
82
+ end
83
+
84
+ private
85
+
86
+ def fetch_view view_name, opts, &block
87
+ database.view(view_name, opts, &block)
88
+ end
89
+
90
+ end
91
+ end
@@ -0,0 +1,105 @@
1
+
2
+ #
3
+ # CouchRest::Document
4
+ #
5
+ # Provides basic functions for controlling documents returned from
6
+ # the CouchDB database and provides methods to act as a wrapper around
7
+ # a Hash of @_attributes.
8
+ #
9
+ # The idea is to provide the basic functionality of a Hash, just
10
+ # enought to support the needs of CouchRest, but not inherit all
11
+ # of the functionality found in a basic Hash.
12
+ #
13
+ # A Response is similar to Rails' HashWithIndifferentAccess as all
14
+ # requests will convert the keys into Symbols and be stored in the
15
+ # master hash as such.
16
+ #
17
+
18
+ module CouchRest
19
+ class Document
20
+ include CouchRest::Attributes
21
+ include CouchRest::Attachments
22
+ extend CouchRest::InheritableAttributes
23
+
24
+ couchrest_inheritable_accessor :database
25
+ attr_accessor :database
26
+
27
+ def id
28
+ self['_id']
29
+ end
30
+ def id=(id)
31
+ self['_id'] = id
32
+ end
33
+ def rev
34
+ self['_rev']
35
+ end
36
+
37
+ # returns true if the document has never been saved
38
+ def new?
39
+ !rev
40
+ end
41
+ alias :new_document? :new?
42
+
43
+ # Saves the document to the db using create or update. Also runs the :save
44
+ # callbacks. Sets the <tt>_id</tt> and <tt>_rev</tt> fields based on
45
+ # CouchDB's response.
46
+ # If <tt>bulk</tt> is <tt>true</tt> (defaults to false) the document is cached for bulk save.
47
+ def save(bulk = false)
48
+ raise ArgumentError, "doc.database required for saving" unless database
49
+ result = database.save_doc self, bulk
50
+ result['ok']
51
+ end
52
+
53
+ # Deletes the document from the database. Runs the :delete callbacks.
54
+ # Removes the <tt>_id</tt> and <tt>_rev</tt> fields, preparing the
55
+ # document to be saved to a new <tt>_id</tt>.
56
+ # If <tt>bulk</tt> is <tt>true</tt> (defaults to false) the document won't
57
+ # actually be deleted from the db until bulk save.
58
+ def destroy(bulk = false)
59
+ raise ArgumentError, "doc.database required to destroy" unless database
60
+ result = database.delete_doc(self, bulk)
61
+ if result['ok']
62
+ self['_rev'] = nil
63
+ self['_id'] = nil
64
+ end
65
+ result['ok']
66
+ end
67
+
68
+ # copies the document to a new id. If the destination id currently exists, a rev must be provided.
69
+ # <tt>dest</tt> can take one of two forms if overwriting: "id_to_overwrite?rev=revision" or the actual doc
70
+ # hash with a '_rev' key
71
+ def copy(dest)
72
+ raise ArgumentError, "doc.database required to copy" unless database
73
+ result = database.copy_doc(self, dest)
74
+ result['ok']
75
+ end
76
+
77
+ # Returns the CouchDB uri for the document
78
+ def uri(append_rev = false)
79
+ return nil if new?
80
+ couch_uri = "#{database.root}/#{CGI.escape(id)}"
81
+ if append_rev == true
82
+ couch_uri << "?rev=#{rev}"
83
+ elsif append_rev.kind_of?(Integer)
84
+ couch_uri << "?rev=#{append_rev}"
85
+ end
86
+ couch_uri
87
+ end
88
+
89
+ # Returns the document's database
90
+ def database
91
+ @database || self.class.database
92
+ end
93
+
94
+ class << self
95
+ # override the CouchRest::Model-wide default_database
96
+ # This is not a thread safe operation, do not change the model
97
+ # database at runtime.
98
+ def use_database(db)
99
+ self.database = db
100
+ end
101
+ end
102
+
103
+ end
104
+
105
+ end
@@ -0,0 +1,29 @@
1
+ module CouchRest
2
+ module Attachments
3
+
4
+ # saves an attachment directly to couchdb
5
+ def put_attachment(name, file, options={})
6
+ raise ArgumentError, "doc must be saved" unless self.rev
7
+ raise ArgumentError, "doc.database required to put_attachment" unless database
8
+ result = database.put_attachment(self, name, file, options)
9
+ self['_rev'] = result['rev']
10
+ result['ok']
11
+ end
12
+
13
+ # returns an attachment's data
14
+ def fetch_attachment(name)
15
+ raise ArgumentError, "doc must be saved" unless self.rev
16
+ raise ArgumentError, "doc.database required to put_attachment" unless database
17
+ database.fetch_attachment(self, name)
18
+ end
19
+
20
+ # deletes an attachment directly from couchdb
21
+ def delete_attachment(name, force=false)
22
+ raise ArgumentError, "doc.database required to delete_attachment" unless database
23
+ result = database.delete_attachment(self, name, force)
24
+ self['_rev'] = result['rev']
25
+ result['ok']
26
+ end
27
+
28
+ end
29
+ end
@@ -0,0 +1,103 @@
1
+ module CouchRest
2
+ class Pager
3
+ attr_accessor :db
4
+ def initialize db
5
+ @db = db
6
+ end
7
+
8
+ def all_docs(limit=100, &block)
9
+ startkey = nil
10
+ oldend = nil
11
+
12
+ while docrows = request_all_docs(limit+1, startkey)
13
+ startkey = docrows.last['key']
14
+ docrows.pop if docrows.length > limit
15
+ if oldend == startkey
16
+ break
17
+ end
18
+ yield(docrows)
19
+ oldend = startkey
20
+ end
21
+ end
22
+
23
+ def key_reduce(view, limit=2000, firstkey = nil, lastkey = nil, &block)
24
+ # start with no keys
25
+ startkey = firstkey
26
+ # lastprocessedkey = nil
27
+ keepgoing = true
28
+
29
+ while keepgoing && viewrows = request_view(view, limit, startkey)
30
+ startkey = viewrows.first['key']
31
+ endkey = viewrows.last['key']
32
+
33
+ if (startkey == endkey)
34
+ # we need to rerequest to get a bigger page
35
+ # so we know we have all the rows for that key
36
+ viewrows = @db.view(view, :key => startkey)['rows']
37
+ # we need to do an offset thing to find the next startkey
38
+ # otherwise we just get stuck
39
+ lastdocid = viewrows.last['id']
40
+ fornextloop = @db.view(view, :startkey => startkey, :startkey_docid => lastdocid, :limit => 2)['rows']
41
+
42
+ newendkey = fornextloop.last['key']
43
+ if (newendkey == endkey)
44
+ keepgoing = false
45
+ else
46
+ startkey = newendkey
47
+ end
48
+ rows = viewrows
49
+ else
50
+ rows = []
51
+ for r in viewrows
52
+ if (lastkey && r['key'] == lastkey)
53
+ keepgoing = false
54
+ break
55
+ end
56
+ break if (r['key'] == endkey)
57
+ rows << r
58
+ end
59
+ startkey = endkey
60
+ end
61
+
62
+ key = :begin
63
+ values = []
64
+
65
+ rows.each do |r|
66
+ if key != r['key']
67
+ # we're on a new key, yield the old first and then reset
68
+ yield(key, values) if key != :begin
69
+ key = r['key']
70
+ values = []
71
+ end
72
+ # keep accumulating
73
+ values << r['value']
74
+ end
75
+ yield(key, values)
76
+
77
+ end
78
+ end
79
+
80
+ private
81
+
82
+ def request_all_docs limit, startkey = nil
83
+ opts = {}
84
+ opts[:limit] = limit if limit
85
+ opts[:startkey] = startkey if startkey
86
+ results = @db.documents(opts)
87
+ rows = results['rows']
88
+ rows unless rows.length == 0
89
+ end
90
+
91
+ def request_view view, limit = nil, startkey = nil, endkey = nil
92
+ opts = {}
93
+ opts[:limit] = limit if limit
94
+ opts[:startkey] = startkey if startkey
95
+ opts[:endkey] = endkey if endkey
96
+
97
+ results = @db.view(view, opts)
98
+ rows = results['rows']
99
+ rows unless rows.length == 0
100
+ end
101
+
102
+ end
103
+ end
@@ -0,0 +1,60 @@
1
+ module CouchRest
2
+ class Streamer
3
+
4
+ attr_accessor :default_curl_opts
5
+
6
+ def initialize
7
+ self.default_curl_opts = "--silent --no-buffer --tcp-nodelay -H \"Content-Type: application/json\""
8
+ end
9
+
10
+ def view(*args)
11
+ raise "CouchRest::Streamer#view is deprecated. Please use Database#view with block."
12
+ end
13
+
14
+ def get(url, &block)
15
+ open_pipe("curl #{default_curl_opts} \"#{url}\"", &block)
16
+ end
17
+
18
+ def post(url, params = {}, &block)
19
+ open_pipe("curl #{default_curl_opts} -d \"#{escape_quotes(MultiJson.encode(params))}\" \"#{url}\"", &block)
20
+ end
21
+
22
+ protected
23
+
24
+ def escape_quotes(data)
25
+ data.gsub(/"/, '\"')
26
+ end
27
+
28
+ def open_pipe(cmd, &block)
29
+ first = nil
30
+ prev = nil
31
+ IO.popen(cmd) do |f|
32
+ first = f.gets # discard header
33
+ while line = f.gets
34
+ row = parse_line(line)
35
+ block.call row unless row.nil? # last line "}]" discarded
36
+ prev = line
37
+ end
38
+ end
39
+
40
+ raise RestClient::ServerBrokeConnection if $? && $?.exitstatus != 0
41
+
42
+ parse_first(first, prev)
43
+ end
44
+
45
+ def parse_line line
46
+ return nil unless line
47
+ if /(\{.*\}),?/.match(line.chomp)
48
+ MultiJson.decode($1)
49
+ end
50
+ end
51
+
52
+ def parse_first first, last
53
+ return nil unless first
54
+ header = MultiJson.decode(last ? first + last : first)
55
+ header.delete("rows")
56
+ header
57
+ end
58
+
59
+ end
60
+ end
@@ -0,0 +1,51 @@
1
+ module CouchRest
2
+ class Upgrade
3
+ attr_accessor :olddb, :newdb, :dbname
4
+ def initialize dbname, old_couch, new_couch
5
+ @dbname = dbname
6
+ @olddb = old_couch.database dbname
7
+ @newdb = new_couch.database!(dbname)
8
+ @bulk_docs = []
9
+ end
10
+ def clone!
11
+ puts "#{dbname} - #{olddb.info['doc_count']} docs"
12
+ streamer = CouchRest::Streamer.new(olddb)
13
+ streamer.view("_all_docs_by_seq") do |row|
14
+ load_row_docs(row) if row
15
+ maybe_flush_bulks
16
+ end
17
+ flush_bulks!
18
+ end
19
+
20
+ private
21
+
22
+ def maybe_flush_bulks
23
+ flush_bulks! if (@bulk_docs.length > 99)
24
+ end
25
+
26
+ def flush_bulks!
27
+ url = CouchRest.paramify_url "#{@newdb.uri}/_bulk_docs", {:all_or_nothing => true}
28
+ puts "posting #{@bulk_docs.length} bulk docs to #{url}"
29
+ begin
30
+ CouchRest.post url, {:docs => @bulk_docs}
31
+ @bulk_docs = []
32
+ rescue Exception => e
33
+ puts e.response
34
+ raise e
35
+ end
36
+ end
37
+
38
+ def load_row_docs(row)
39
+ results = @olddb.get(row["id"], {:open_revs => "all", :attachments => true})
40
+ results.select{|r|r["ok"]}.each do |r|
41
+ doc = r["ok"]
42
+ if /^_/.match(doc["_id"]) && !/^_design/.match(doc["_id"])
43
+ puts "invalid docid #{doc["_id"]} -- trimming"
44
+ doc["_id"] = doc["_id"].sub('_','')
45
+ end
46
+ doc.delete('_rev')
47
+ @bulk_docs << doc
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,263 @@
1
+ ####################################
2
+ # USAGE
3
+ #
4
+ # in your rack.rb file
5
+ # require this file and then:
6
+ #
7
+ # couch = CouchRest.new
8
+ # LOG_DB = couch.database!('couchrest-logger')
9
+ # use CouchRest::Logger, LOG_DB
10
+ #
11
+ # Note:
12
+ # to require just this middleware, if you have the gem installed do:
13
+ # require 'couchrest/middlewares/logger'
14
+ #
15
+ # For log processing examples, see examples at the bottom of this file
16
+
17
+ module CouchRest
18
+ class Logger
19
+
20
+ def self.log
21
+ Thread.current["couchrest.logger"] ||= {:queries => []}
22
+ end
23
+
24
+ def initialize(app, db=nil)
25
+ @app = app
26
+ @db = db
27
+ end
28
+
29
+ def self.record(log_info)
30
+ log[:queries] << log_info
31
+ end
32
+
33
+ def log
34
+ Thread.current["couchrest.logger"] ||= {:queries => []}
35
+ end
36
+
37
+ def reset_log
38
+ Thread.current["couchrest.logger"] = nil
39
+ end
40
+
41
+ def call(env)
42
+ reset_log
43
+ log['started_at'] = Time.now
44
+ log['env'] = env
45
+ log['url'] = 'http://' + env['HTTP_HOST'] + env['REQUEST_URI']
46
+ response = @app.call(env)
47
+ log['ended_at'] = Time.now
48
+ log['duration'] = log['ended_at'] - log['started_at']
49
+ # let's report the log in a different thread so we don't slow down the app
50
+ @db ? Thread.new(@db, log){|db, rlog| db.save_doc(rlog);} : p(log.inspect)
51
+ response
52
+ end
53
+ end
54
+ end
55
+
56
+ # inject our logger into key RestClient methods
57
+ module RestClient
58
+
59
+ def self.get(uri, headers=nil)
60
+ start_query = Time.now
61
+ log = {:method => :get, :uri => uri, :headers => headers}
62
+ response = super(uri, headers=nil)
63
+ end_query = Time.now
64
+ log[:duration] = (end_query - start_query)
65
+ CouchRest::Logger.record(log)
66
+ response
67
+ end
68
+
69
+ def self.post(uri, payload, headers=nil)
70
+ start_query = Time.now
71
+ log = {:method => :post, :uri => uri, :payload => (payload ? (MultiJson.decode(payload) rescue 'parsing error') : nil), :headers => headers}
72
+ response = super(uri, payload, headers=nil)
73
+ end_query = Time.now
74
+ log[:duration] = (end_query - start_query)
75
+ CouchRest::Logger.record(log)
76
+ response
77
+ end
78
+
79
+ def self.put(uri, payload, headers=nil)
80
+ start_query = Time.now
81
+ log = {:method => :put, :uri => uri, :payload => (payload ? (MultiJson.decode(payload) rescue 'parsing error') : nil), :headers => headers}
82
+ response = super(uri, payload, headers=nil)
83
+ end_query = Time.now
84
+ log[:duration] = (end_query - start_query)
85
+ CouchRest::Logger.record(log)
86
+ response
87
+ end
88
+
89
+ def self.delete(uri, headers=nil)
90
+ start_query = Time.now
91
+ log = {:method => :delete, :uri => uri, :headers => headers}
92
+ response = super(uri, headers=nil)
93
+ end_query = Time.now
94
+ log[:duration] = (end_query - start_query)
95
+ CouchRest::Logger.record(log)
96
+ response
97
+ end
98
+
99
+ end
100
+
101
+
102
+ # Advanced usage example
103
+ #
104
+ #
105
+ # # DB VIEWS
106
+ # by_url = {
107
+ # :map =>
108
+ # "function(doc) {
109
+ # if(doc['url']){ emit(doc['url'], 1) };
110
+ # }",
111
+ # :reduce =>
112
+ # 'function (key, values, rereduce) {
113
+ # return(sum(values));
114
+ # };'
115
+ # }
116
+ # req_duration = {
117
+ # :map =>
118
+ # "function(doc) {
119
+ # if(doc['duration']){ emit(doc['url'], doc['duration']) };
120
+ # }",
121
+ # :reduce =>
122
+ # 'function (key, values, rereduce) {
123
+ # return(sum(values)/values.length);
124
+ # };'
125
+ # }
126
+ #
127
+ # query_duration = {
128
+ # :map =>
129
+ # "function(doc) {
130
+ # if(doc['queries']){
131
+ # doc.queries.forEach(function(query){
132
+ # if(query['duration'] && query['method']){
133
+ # emit(query['method'], query['duration'])
134
+ # }
135
+ # });
136
+ # };
137
+ # }" ,
138
+ # :reduce =>
139
+ # 'function (key, values, rereduce) {
140
+ # return(sum(values)/values.length);
141
+ # };'
142
+ # }
143
+ #
144
+ # action_queries = {
145
+ # :map =>
146
+ # "function(doc) {
147
+ # if(doc['queries']){
148
+ # emit(doc['url'], doc['queries'].length)
149
+ # };
150
+ # }",
151
+ # :reduce =>
152
+ # 'function (key, values, rereduce) {
153
+ # return(sum(values)/values.length);
154
+ # };'
155
+ # }
156
+ #
157
+ # action_time_spent_in_db = {
158
+ # :map =>
159
+ # "function(doc) {
160
+ # if(doc['queries']){
161
+ # var totalDuration = 0;
162
+ # doc.queries.forEach(function(query){
163
+ # totalDuration += query['duration']
164
+ # })
165
+ # emit(doc['url'], totalDuration)
166
+ # };
167
+ # }",
168
+ # :reduce =>
169
+ # 'function (key, values, rereduce) {
170
+ # return(sum(values)/values.length);
171
+ # };'
172
+ # }
173
+ #
174
+ # show_queries = %Q~function(doc, req) {
175
+ # var body = ""
176
+ # body += "<h1>" + doc['url'] + "</h1>"
177
+ # body += "<h2>Request duration in seconds: " + doc['duration'] + "</h2>"
178
+ # body += "<h3>" + doc['queries'].length + " queries</h3><ul>"
179
+ # if (doc.queries){
180
+ # doc.queries.forEach(function(query){
181
+ # body += "<li>"+ query['uri'] +"</li>"
182
+ # });
183
+ # };
184
+ # body += "</ul>"
185
+ # if(doc){ return { body: body} }
186
+ # }~
187
+ #
188
+ #
189
+ # couch = CouchRest.new
190
+ # LOG_DB = couch.database!('couchrest-logger')
191
+ # design_doc = LOG_DB.get("_design/stats") rescue nil
192
+ # LOG_DB.delete_doc design_doc rescue nil
193
+ # LOG_DB.save_doc({
194
+ # "_id" => "_design/stats",
195
+ # :views => {
196
+ # :by_url => by_url,
197
+ # :request_duration => req_duration,
198
+ # :query_duration => query_duration,
199
+ # :action_queries => action_queries,
200
+ # :action_time_spent_in_db => action_time_spent_in_db
201
+ # },
202
+ # :shows => {
203
+ # :queries => show_queries
204
+ # }
205
+ # })
206
+ #
207
+ # module CouchRest
208
+ # class Logger
209
+ #
210
+ # def self.roundup(value)
211
+ # begin
212
+ # value = Float(value)
213
+ # (value * 100).round.to_f / 100
214
+ # rescue
215
+ # value
216
+ # end
217
+ # end
218
+ #
219
+ # # Usage example:
220
+ # # CouchRest::Logger.average_request_duration(LOG_DB)['rows'].first['value']
221
+ # def self.average_request_duration(db)
222
+ # raw = db.view('stats/request_duration', :reduce => true)
223
+ # (raw.has_key?('rows') && !raw['rows'].empty?) ? roundup(raw['rows'].first['value']) : 'not available yet'
224
+ # end
225
+ #
226
+ # def self.average_query_duration(db)
227
+ # raw = db.view('stats/query_duration', :reduce => true)
228
+ # (raw.has_key?('rows') && !raw['rows'].empty?) ? roundup(raw['rows'].first['value']) : 'not available yet'
229
+ # end
230
+ #
231
+ # def self.average_get_query_duration(db)
232
+ # raw = db.view('stats/query_duration', :key => 'get', :reduce => true)
233
+ # (raw.has_key?('rows') && !raw['rows'].empty?) ? roundup(raw['rows'].first['value']) : 'not available yet'
234
+ # end
235
+ #
236
+ # def self.average_post_query_duration(db)
237
+ # raw = db.view('stats/query_duration', :key => 'post', :reduce => true)
238
+ # (raw.has_key?('rows') && !raw['rows'].empty?) ? roundup(raw['rows'].first['value']) : 'not available yet'
239
+ # end
240
+ #
241
+ # def self.average_queries_per_action(db)
242
+ # raw = db.view('stats/action_queries', :reduce => true)
243
+ # (raw.has_key?('rows') && !raw['rows'].empty?) ? roundup(raw['rows'].first['value']) : 'not available yet'
244
+ # end
245
+ #
246
+ # def self.average_db_time_per_action(db)
247
+ # raw = db.view('stats/action_time_spent_in_db', :reduce => true)
248
+ # (raw.has_key?('rows') && !raw['rows'].empty?) ? roundup(raw['rows'].first['value']) : 'not available yet'
249
+ # end
250
+ #
251
+ # def self.stats(db)
252
+ # Thread.new(db){|db|
253
+ # puts "=== STATS ===\n"
254
+ # puts "average request duration: #{average_request_duration(db)}\n"
255
+ # puts "average query duration: #{average_query_duration(db)}\n"
256
+ # puts "average queries per action : #{average_queries_per_action(db)}\n"
257
+ # puts "average time spent in DB (per action): #{average_db_time_per_action(db)}\n"
258
+ # puts "===============\n"
259
+ # }
260
+ # end
261
+ #
262
+ # end
263
+ # end