dpla-couchrest 1.2.1.pre.dpla

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 (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