derfred-couchrest 0.12.6

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