derfred-couchrest 0.12.6

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 (47) hide show
  1. data/LICENSE +176 -0
  2. data/README.md +68 -0
  3. data/Rakefile +66 -0
  4. data/THANKS.md +18 -0
  5. data/examples/model/example.rb +138 -0
  6. data/examples/word_count/markov +38 -0
  7. data/examples/word_count/views/books/chunked-map.js +3 -0
  8. data/examples/word_count/views/books/united-map.js +1 -0
  9. data/examples/word_count/views/markov/chain-map.js +6 -0
  10. data/examples/word_count/views/markov/chain-reduce.js +7 -0
  11. data/examples/word_count/views/word_count/count-map.js +6 -0
  12. data/examples/word_count/views/word_count/count-reduce.js +3 -0
  13. data/examples/word_count/word_count.rb +46 -0
  14. data/examples/word_count/word_count_query.rb +40 -0
  15. data/examples/word_count/word_count_views.rb +26 -0
  16. data/lib/couchrest/commands/generate.rb +71 -0
  17. data/lib/couchrest/commands/push.rb +103 -0
  18. data/lib/couchrest/core/database.rb +314 -0
  19. data/lib/couchrest/core/design.rb +89 -0
  20. data/lib/couchrest/core/document.rb +101 -0
  21. data/lib/couchrest/core/model.rb +615 -0
  22. data/lib/couchrest/core/server.rb +88 -0
  23. data/lib/couchrest/core/view.rb +4 -0
  24. data/lib/couchrest/helper/pager.rb +103 -0
  25. data/lib/couchrest/helper/streamer.rb +44 -0
  26. data/lib/couchrest/monkeypatches.rb +99 -0
  27. data/lib/couchrest.rb +161 -0
  28. data/spec/couchrest/core/couchrest_spec.rb +201 -0
  29. data/spec/couchrest/core/database_spec.rb +740 -0
  30. data/spec/couchrest/core/design_spec.rb +131 -0
  31. data/spec/couchrest/core/document_spec.rb +311 -0
  32. data/spec/couchrest/core/model_spec.rb +855 -0
  33. data/spec/couchrest/helpers/pager_spec.rb +122 -0
  34. data/spec/couchrest/helpers/streamer_spec.rb +23 -0
  35. data/spec/fixtures/attachments/README +3 -0
  36. data/spec/fixtures/attachments/couchdb.png +0 -0
  37. data/spec/fixtures/attachments/test.html +11 -0
  38. data/spec/fixtures/views/lib.js +3 -0
  39. data/spec/fixtures/views/test_view/lib.js +3 -0
  40. data/spec/fixtures/views/test_view/only-map.js +4 -0
  41. data/spec/fixtures/views/test_view/test-map.js +3 -0
  42. data/spec/fixtures/views/test_view/test-reduce.js +3 -0
  43. data/spec/spec.opts +6 -0
  44. data/spec/spec_helper.rb +21 -0
  45. data/utils/remap.rb +27 -0
  46. data/utils/subset.rb +30 -0
  47. metadata +143 -0
@@ -0,0 +1,88 @@
1
+ module CouchRest
2
+ class Server
3
+ attr_accessor :uri, :uuid_batch_count, :available_databases
4
+ def initialize(server = 'http://127.0.0.1:5984', uuid_batch_count = 1000)
5
+ @uri = server
6
+ @uuid_batch_count = uuid_batch_count
7
+ end
8
+
9
+ # Lists all "available" databases.
10
+ # An available database, is a database that was specified
11
+ # as avaiable by your code.
12
+ # It allows to define common databases to use and reuse in your code
13
+ def available_databases
14
+ @available_databases ||= {}
15
+ end
16
+
17
+ # Adds a new available database and create it unless it already exists
18
+ #
19
+ # Example:
20
+ #
21
+ # @couch = CouchRest::Server.new
22
+ # @couch.define_available_database(:default, "tech-blog")
23
+ #
24
+ def define_available_database(reference, db_name, create_unless_exists = true)
25
+ available_databases[reference.to_sym] = create_unless_exists ? database!(db_name) : database(db_name)
26
+ end
27
+
28
+ # Checks that a database is set as available
29
+ #
30
+ # Example:
31
+ #
32
+ # @couch.available_database?(:default)
33
+ #
34
+ def available_database?(ref_or_name)
35
+ ref_or_name.is_a?(Symbol) ? available_databases.keys.include?(ref_or_name) : available_databases.values.map{|db| db.name}.include?(ref_or_name)
36
+ end
37
+
38
+ def default_database=(name, create_unless_exists = true)
39
+ define_available_database(:default, name, create_unless_exists = true)
40
+ end
41
+
42
+ def default_database
43
+ available_databases[:default]
44
+ end
45
+
46
+ # Lists all databases on the server
47
+ def databases
48
+ CouchRest.get "#{@uri}/_all_dbs"
49
+ end
50
+
51
+ # Returns a CouchRest::Database for the given name
52
+ def database(name)
53
+ CouchRest::Database.new(self, name)
54
+ end
55
+
56
+ # Creates the database if it doesn't exist
57
+ def database!(name)
58
+ create_db(name) rescue nil
59
+ database(name)
60
+ end
61
+
62
+ # GET the welcome message
63
+ def info
64
+ CouchRest.get "#{@uri}/"
65
+ end
66
+
67
+ # Create a database
68
+ def create_db(name)
69
+ CouchRest.put "#{@uri}/#{name}"
70
+ database(name)
71
+ end
72
+
73
+ # Restart the CouchDB instance
74
+ def restart!
75
+ CouchRest.post "#{@uri}/_restart"
76
+ end
77
+
78
+ # Retrive an unused UUID from CouchDB. Server instances manage caching a list of unused UUIDs.
79
+ def next_uuid(count = @uuid_batch_count)
80
+ @uuids ||= []
81
+ if @uuids.empty?
82
+ @uuids = CouchRest.post("#{@uri}/_uuids?count=#{count}")["uuids"]
83
+ end
84
+ @uuids.pop
85
+ end
86
+
87
+ end
88
+ end
@@ -0,0 +1,4 @@
1
+ module CouchRest
2
+ class View
3
+ end
4
+ 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,44 @@
1
+ module CouchRest
2
+ class Streamer
3
+ attr_accessor :db
4
+ def initialize db
5
+ @db = db
6
+ end
7
+
8
+ # Stream a view, yielding one row at a time. Shells out to <tt>curl</tt> to keep RAM usage low when you have millions of rows.
9
+ def view name, params = nil, &block
10
+ urlst = /^_/.match(name) ? "#{@db.root}/#{name}" : "#{@db.root}/_view/#{name}"
11
+ url = CouchRest.paramify_url urlst, params
12
+ # puts "stream #{url}"
13
+ first = nil
14
+ IO.popen("curl --silent #{url}") do |view|
15
+ first = view.gets # discard header
16
+ while line = view.gets
17
+ row = parse_line(line)
18
+ block.call row
19
+ end
20
+ end
21
+ parse_first(first)
22
+ end
23
+
24
+ private
25
+
26
+ def parse_line line
27
+ return nil unless line
28
+ if /(\{.*\}),?/.match(line.chomp)
29
+ JSON.parse($1)
30
+ end
31
+ end
32
+
33
+ def parse_first first
34
+ return nil unless first
35
+ parts = first.split(',')
36
+ parts.pop
37
+ line = parts.join(',')
38
+ JSON.parse("#{line}}")
39
+ rescue
40
+ nil
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,99 @@
1
+ # This file must be loaded after the JSON gem and any other library that beats up the Time class.
2
+ class Time
3
+ # This date format sorts lexicographically
4
+ # and is compatible with Javascript's <tt>new Date(time_string)</tt> constructor.
5
+ # Note this this format stores all dates in UTC so that collation
6
+ # order is preserved. (There's no longer a need to set <tt>ENV['TZ'] = 'UTC'</tt>
7
+ # in your application.)
8
+
9
+ def to_json(options = nil)
10
+ u = self.utc
11
+ %("#{u.strftime("%Y/%m/%d %H:%M:%S +0000")}")
12
+ end
13
+
14
+ # Decodes the JSON time format to a UTC time.
15
+ # Based on Time.parse from ActiveSupport. ActiveSupport's version
16
+ # is more complete, returning a time in your current timezone,
17
+ # rather than keeping the time in UTC. YMMV.
18
+ # def self.parse string, fallback=nil
19
+ # d = DateTime.parse(string).new_offset
20
+ # self.utc(d.year, d.month, d.day, d.hour, d.min, d.sec)
21
+ # rescue
22
+ # fallback
23
+ # end
24
+ end
25
+
26
+ # Monkey patch for faster net/http io
27
+ if RUBY_VERSION.to_f < 1.9
28
+ class Net::BufferedIO #:nodoc:
29
+ alias :old_rbuf_fill :rbuf_fill
30
+ def rbuf_fill
31
+ begin
32
+ @rbuf << @io.read_nonblock(65536)
33
+ rescue Errno::EWOULDBLOCK
34
+ if IO.select([@io], nil, nil, @read_timeout)
35
+ @rbuf << @io.read_nonblock(65536)
36
+ else
37
+ raise Timeout::TimeoutError
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ module RestClient
45
+ def self.copy(url, headers={})
46
+ Request.execute(:method => :copy,
47
+ :url => url,
48
+ :headers => headers)
49
+ end
50
+
51
+ def self.move(url, headers={})
52
+ Request.execute(:method => :move,
53
+ :url => url,
54
+ :headers => headers)
55
+ end
56
+
57
+ class Request
58
+ def transmit(uri, req, payload)
59
+ setup_credentials(req)
60
+
61
+ Thread.current[:host] ||= uri.host
62
+ Thread.current[:port] ||= uri.port
63
+
64
+ net = net_http_class.new(uri.host, uri.port)
65
+
66
+ if Thread.current[:connection].nil? || Thread.current[:host] != uri.host
67
+ Thread.current[:connection].finish if (Thread.current[:connection] && Thread.current[:connection].started?)
68
+ net.use_ssl = uri.is_a?(URI::HTTPS)
69
+ net.verify_mode = OpenSSL::SSL::VERIFY_NONE
70
+ Thread.current[:connection] = net
71
+ Thread.current[:connection].start
72
+ end
73
+
74
+ display_log request_log
75
+ http = Thread.current[:connection]
76
+
77
+ http.read_timeout = @timeout if @timeout
78
+ begin
79
+ res = http.request(req, payload)
80
+ rescue
81
+ # p "Net::HTTP connection failed, reconnecting"
82
+ Thread.current[:connection].finish
83
+ http = Thread.current[:connection] = net
84
+ Thread.current[:connection].start
85
+ res = http.request(req, payload)
86
+ display_log response_log(res)
87
+ process_result res
88
+ else
89
+ display_log response_log(res)
90
+ process_result res
91
+ end
92
+
93
+ rescue EOFError
94
+ raise RestClient::ServerBrokeConnection
95
+ rescue Timeout::Error
96
+ raise RestClient::RequestTimeout
97
+ end
98
+ end
99
+ end
data/lib/couchrest.rb ADDED
@@ -0,0 +1,161 @@
1
+ # Copyright 2008 J. Chris Anderson
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require "rubygems"
16
+ require 'json'
17
+ require 'rest_client'
18
+
19
+ $:.unshift File.dirname(__FILE__) unless
20
+ $:.include?(File.dirname(__FILE__)) ||
21
+ $:.include?(File.expand_path(File.dirname(__FILE__)))
22
+
23
+
24
+ require 'couchrest/monkeypatches'
25
+
26
+ # = CouchDB, close to the metal
27
+ module CouchRest
28
+ VERSION = '0.12.6'
29
+
30
+ autoload :Server, 'couchrest/core/server'
31
+ autoload :Database, 'couchrest/core/database'
32
+ autoload :Document, 'couchrest/core/document'
33
+ autoload :Design, 'couchrest/core/design'
34
+ autoload :View, 'couchrest/core/view'
35
+ autoload :Model, 'couchrest/core/model'
36
+ autoload :Pager, 'couchrest/helper/pager'
37
+ autoload :FileManager, 'couchrest/helper/file_manager'
38
+ autoload :Streamer, 'couchrest/helper/streamer'
39
+
40
+ autoload :ExtendedDocument, 'couchrest/more/extended_document'
41
+
42
+ require File.join(File.dirname(__FILE__), 'couchrest', 'mixins')
43
+
44
+ # The CouchRest module methods handle the basic JSON serialization
45
+ # and deserialization, as well as query parameters. The module also includes
46
+ # some helpers for tasks like instantiating a new Database or Server instance.
47
+ class << self
48
+
49
+ # todo, make this parse the url and instantiate a Server or Database instance
50
+ # depending on the specificity.
51
+ def new(*opts)
52
+ Server.new(*opts)
53
+ end
54
+
55
+ def parse url
56
+ case url
57
+ when /^http:\/\/(.*)\/(.*)\/(.*)/
58
+ host = $1
59
+ db = $2
60
+ docid = $3
61
+ when /^http:\/\/(.*)\/(.*)/
62
+ host = $1
63
+ db = $2
64
+ when /^http:\/\/(.*)/
65
+ host = $1
66
+ when /(.*)\/(.*)\/(.*)/
67
+ host = $1
68
+ db = $2
69
+ docid = $3
70
+ when /(.*)\/(.*)/
71
+ host = $1
72
+ db = $2
73
+ else
74
+ db = url
75
+ end
76
+
77
+ db = nil if db && db.empty?
78
+
79
+ {
80
+ :host => host || "127.0.0.1:5984",
81
+ :database => db,
82
+ :doc => docid
83
+ }
84
+ end
85
+
86
+ # set proxy for RestClient to use
87
+ def proxy url
88
+ RestClient.proxy = url
89
+ end
90
+
91
+ # ensure that a database exists
92
+ # creates it if it isn't already there
93
+ # returns it after it's been created
94
+ def database! url
95
+ parsed = parse url
96
+ cr = CouchRest.new(parsed[:host])
97
+ cr.database!(parsed[:database])
98
+ end
99
+
100
+ def database url
101
+ parsed = parse url
102
+ cr = CouchRest.new(parsed[:host])
103
+ cr.database(parsed[:database])
104
+ end
105
+
106
+ def put_raw(uri, doc=nil)
107
+ payload = doc.to_json if doc
108
+ RestClient.put(uri, payload)
109
+ end
110
+
111
+ def get_raw(uri)
112
+ RestClient.get(uri)
113
+ end
114
+
115
+ def post_raw(uri, doc=nil)
116
+ payload = doc.to_json if doc
117
+ RestClient.post(uri, payload)
118
+ end
119
+
120
+ def delete_raw(uri)
121
+ RestClient.delete(uri)
122
+ end
123
+
124
+
125
+
126
+ def put uri, doc = nil
127
+ JSON.parse(put_raw(uri, doc))
128
+ end
129
+
130
+ def get uri
131
+ JSON.parse(get_raw(uri), :max_nesting => false)
132
+ end
133
+
134
+ def post uri, doc = nil
135
+ JSON.parse(post_raw(uri, doc))
136
+ end
137
+
138
+ def delete uri
139
+ JSON.parse(delete_raw(uri))
140
+ end
141
+
142
+ def copy uri, destination
143
+ JSON.parse(RestClient.copy(uri, {'Destination' => destination}))
144
+ end
145
+
146
+ def move uri, destination
147
+ JSON.parse(RestClient.move(uri, {'Destination' => destination}))
148
+ end
149
+
150
+ def paramify_url url, params = {}
151
+ if params && !params.empty?
152
+ query = params.collect do |k,v|
153
+ v = v.to_json if %w{key startkey endkey}.include?(k.to_s)
154
+ "#{k}=#{CGI.escape(v.to_s)}"
155
+ end.join("&")
156
+ url = "#{url}?#{query}"
157
+ end
158
+ url
159
+ end
160
+ end # class << self
161
+ end