jchris-couchrest 0.9.1 → 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown ADDED
@@ -0,0 +1,51 @@
1
+ ## CouchRest - CouchDB, close to the metal
2
+
3
+ CouchRest is based on [CouchDB's couch.js test library](http://svn.apache.org/repos/asf/incubator/couchdb/trunk/share/www/script/couch.js), which I find to be concise, clear, and well designed. CouchRest lightly wraps CouchDB's HTTP API, managing JSON serialization, and remembering the URI-paths to CouchDB's API endpoints so you don't have to.
4
+
5
+ CouchRest's lighweight is designed to make a simple base for application and framework-specific object oriented APIs.
6
+
7
+ ### Easy Install
8
+
9
+ `sudo gem install jchris-couchrest -s http://gems.github.com`
10
+
11
+ ### Relax, it's RESTful
12
+
13
+ The core of Couchrest is Heroku’s excellent REST Client Ruby HTTP wrapper. REST Client takes all the nastyness of Net::HTTP and gives is a pretty face, while still giving you more control than Open-URI. I recommend it anytime you’re interfacing with a well-defined web service.
14
+
15
+ ### Running the Specs
16
+
17
+ The most complete documentation is the spec/ directory. To validate your CouchRest install, from the project root directory run `rake`, or `autotest` (requires RSpec and optionally ZenTest for autotest support).
18
+
19
+ ### Examples
20
+
21
+ Quick Start:
22
+
23
+ # with !, it creates the database if it doesn't already exist
24
+ @db = CouchRest.database!("http://localhost:5984/couchrest-test")
25
+ response = @db.save({:key => 'value', 'another key' => 'another value'})
26
+ doc = @db.get(response['id'])
27
+ puts doc.inspect
28
+
29
+ Bulk Save:
30
+
31
+ @db.bulk_save([
32
+ {"wild" => "and random"},
33
+ {"mild" => "yet local"},
34
+ {"another" => ["set","of","keys"]}
35
+ ])
36
+ # returns ids and revs of the current docs
37
+ puts @db.documents.inspect
38
+
39
+ Creating and Querying Views:
40
+
41
+ @db.save({
42
+ "_id" => "_design/first",
43
+ :views => {
44
+ :test => {
45
+ :map => "function(doc){for(var w in doc){ if(!w.match(/^_/))emit(w,doc[w])}}"
46
+ }
47
+ }
48
+ })
49
+ puts @db.view('first/test')['rows'].inspect
50
+
51
+
data/lib/couch_rest.rb CHANGED
@@ -1,7 +1,27 @@
1
1
  class CouchRest
2
- attr_accessor :uri
3
- def initialize server = 'http://localhost:5984'
2
+ attr_accessor :uri, :uuid_batch_count
3
+ def initialize server = 'http://localhost:5984', uuid_batch_count = 1000
4
4
  @uri = server
5
+ @uuid_batch_count = uuid_batch_count
6
+ end
7
+
8
+ # ensure that a database exists
9
+ # creates it if it isn't already there
10
+ # returns it after it's been created
11
+ def self.database! url
12
+ uri = URI.parse url
13
+ path = uri.path
14
+ uri.path = ''
15
+ cr = CouchRest.new(uri.to_s)
16
+ cr.database!(path)
17
+ end
18
+
19
+ def self.database url
20
+ uri = URI.parse url
21
+ path = uri.path
22
+ uri.path = ''
23
+ cr = CouchRest.new(uri.to_s)
24
+ cr.database(path)
5
25
  end
6
26
 
7
27
  # list all databases on the server
@@ -10,7 +30,13 @@ class CouchRest
10
30
  end
11
31
 
12
32
  def database name
13
- CouchRest::Database.new(@uri, name)
33
+ CouchRest::Database.new(self, name)
34
+ end
35
+
36
+ # creates the database if it doesn't exist
37
+ def database! name
38
+ create_db(path) rescue nil
39
+ database name
14
40
  end
15
41
 
16
42
  # get the welcome message
@@ -18,17 +44,25 @@ class CouchRest
18
44
  CouchRest.get "#{@uri}/"
19
45
  end
20
46
 
21
- # restart the couchdb instance
22
- def restart!
23
- CouchRest.post "#{@uri}/_restart"
24
- end
25
-
26
47
  # create a database
27
48
  def create_db name
28
49
  CouchRest.put "#{@uri}/#{name}"
29
50
  database name
30
51
  end
31
52
 
53
+ # restart the couchdb instance
54
+ def restart!
55
+ CouchRest.post "#{@uri}/_restart"
56
+ end
57
+
58
+ def next_uuid count = @uuid_batch_count
59
+ @uuids ||= []
60
+ if @uuids.empty?
61
+ @uuids = CouchRest.post("#{@uri}/_uuids?count=#{count}")["uuids"]
62
+ end
63
+ @uuids.pop
64
+ end
65
+
32
66
  class << self
33
67
  def put uri, doc = nil
34
68
  payload = doc.to_json if doc
data/lib/couchrest.rb CHANGED
@@ -6,5 +6,6 @@ require File.dirname(__FILE__) + '/couch_rest'
6
6
  require File.dirname(__FILE__) + '/database'
7
7
  require File.dirname(__FILE__) + '/pager'
8
8
  require File.dirname(__FILE__) + '/file_manager'
9
+ require File.dirname(__FILE__) + '/streamer'
9
10
 
10
11
 
data/lib/database.rb CHANGED
@@ -3,10 +3,12 @@ require "base64"
3
3
 
4
4
  class CouchRest
5
5
  class Database
6
- attr_accessor :host, :name
7
- def initialize host, name
6
+ attr_reader :server, :host, :name, :root
7
+
8
+ def initialize server, name
8
9
  @name = name
9
- @host = host
10
+ @server = server
11
+ @host = server.uri
10
12
  @root = "#{host}/#{name}"
11
13
  end
12
14
 
@@ -14,6 +16,10 @@ class CouchRest
14
16
  @root
15
17
  end
16
18
 
19
+ def info
20
+ CouchRest.get @root
21
+ end
22
+
17
23
  def documents params = nil
18
24
  url = CouchRest.paramify_url "#{@root}/_all_docs", params
19
25
  CouchRest.get url
@@ -58,13 +64,18 @@ class CouchRest
58
64
  end
59
65
  if doc['_id']
60
66
  slug = CGI.escape(doc['_id'])
61
- CouchRest.put "#{@root}/#{slug}", doc
62
67
  else
63
- CouchRest.post "#{@root}", doc
68
+ slug = doc['_id'] = @server.next_uuid
64
69
  end
70
+ CouchRest.put "#{@root}/#{slug}", doc
65
71
  end
66
72
 
67
73
  def bulk_save docs
74
+ ids, noids = docs.partition{|d|d['_id']}
75
+ uuid_count = [noids.length, @server.uuid_batch_count].max
76
+ noids.each do |doc|
77
+ doc['_id'] = @server.next_uuid(uuid_count)
78
+ end
68
79
  CouchRest.post "#{@root}/_bulk_docs", {:docs => docs}
69
80
  end
70
81
 
@@ -76,7 +87,9 @@ class CouchRest
76
87
  def delete!
77
88
  CouchRest.delete @root
78
89
  end
90
+
79
91
  private
92
+
80
93
  def encode_attachments attachments
81
94
  attachments.each do |k,v|
82
95
  next if v['stub']
@@ -84,6 +97,7 @@ class CouchRest
84
97
  end
85
98
  attachments
86
99
  end
100
+
87
101
  def base64 data
88
102
  Base64.encode64(data).gsub(/\s/,'')
89
103
  end
data/lib/pager.rb CHANGED
@@ -1,19 +1,3 @@
1
- module Enumerable
2
- def group_by
3
- inject({}) do |grouped, element|
4
- (grouped[yield(element)] ||= []) << element
5
- grouped
6
- end
7
- end unless [].respond_to?(:group_by)
8
-
9
- def group_by_fast
10
- inject({}) do |grouped, element|
11
- (grouped[yield(element)] ||= []) << element
12
- grouped
13
- end
14
- end
15
- end
16
-
17
1
  class CouchRest
18
2
  class Pager
19
3
  attr_accessor :db
@@ -74,12 +58,6 @@ class CouchRest
74
58
  end
75
59
  startkey = endkey
76
60
  end
77
-
78
- # grouped = rows.group_by{|r|r['key']}
79
- # grouped.each do |k, rs|
80
- # vs = rs.collect{|r|r['value']}
81
- # yield(k,vs)
82
- # end
83
61
 
84
62
  key = :begin
85
63
  values = []
data/lib/streamer.rb ADDED
@@ -0,0 +1,29 @@
1
+ class CouchRest
2
+ class Streamer
3
+ attr_accessor :db
4
+ def initialize db
5
+ @db = db
6
+ end
7
+
8
+ def view name, params = nil
9
+ urlst = /^_/.match(name) ? "#{@db.root}/#{name}" : "#{@db.root}/_view/#{name}"
10
+ url = CouchRest.paramify_url urlst, params
11
+ IO.popen("curl --silent #{url}") do |view|
12
+ view.gets # discard header
13
+ while row = parse_line(view.gets)
14
+ yield row
15
+ end
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def parse_line line
22
+ return nil unless line
23
+ if /(\{.*\}),?/.match(line.chomp)
24
+ JSON.parse($1)
25
+ end
26
+ end
27
+
28
+ end
29
+ end
@@ -26,10 +26,12 @@ describe CouchRest do
26
26
  end
27
27
  end
28
28
 
29
- describe "description" do
30
- it "should restart" do
31
- @cr.restart!
32
- end
29
+ it "should restart" do
30
+ @cr.restart!
31
+ end
32
+
33
+ it "should provide one-time access to uuids" do
34
+ @cr.next_uuid.should_not be_nil
33
35
  end
34
36
 
35
37
  describe "initializing a database" do
@@ -40,6 +42,25 @@ describe CouchRest do
40
42
  end
41
43
  end
42
44
 
45
+ describe "easy initializing a database adapter" do
46
+ it "should be possible without an explicit CouchRest instantiation" do
47
+ db = CouchRest.database "http://localhost:5984/couchrest-test"
48
+ db.should be_an_instance_of(CouchRest::Database)
49
+ db.host.should == "http://localhost:5984"
50
+ end
51
+ it "should not create the database automatically" do
52
+ db = CouchRest.database "http://localhost:5984/couchrest-test"
53
+ lambda{db.info}.should raise_error(RestClient::ResourceNotFound)
54
+ end
55
+ end
56
+
57
+ describe "ensuring the db exists" do
58
+ it "should be super easy" do
59
+ db = CouchRest.database! "http://localhost:5984/couchrest-test-2"
60
+ db.info["db_name"].should == 'couchrest-test-2'
61
+ end
62
+ end
63
+
43
64
  describe "successfully creating a database" do
44
65
  it "should start without a database" do
45
66
  @cr.databases.should_not include(TESTDB)
@@ -137,6 +137,17 @@ describe CouchRest::Database do
137
137
  @db.get(r['id'])
138
138
  end
139
139
  end
140
+
141
+ it "should use uuids when ids aren't provided" do
142
+ @db.server.stub!(:next_uuid).and_return('asdf6sgadkfhgsdfusdf')
143
+
144
+ docs = [{'key' => 'value'}, {'_id' => 'totally-uniq'}]
145
+ id_docs = [{'key' => 'value', '_id' => 'asdf6sgadkfhgsdfusdf'}, {'_id' => 'totally-uniq'}]
146
+ CouchRest.should_receive(:post).with("http://localhost:5984/couchrest-test/_bulk_docs", {:docs => id_docs})
147
+
148
+ @db.bulk_save(docs)
149
+ end
150
+
140
151
  it "should add them with uniq ids" do
141
152
  rs = @db.bulk_save([
142
153
  {"_id" => "oneB", "wild" => "and random"},
@@ -147,6 +158,7 @@ describe CouchRest::Database do
147
158
  @db.get(r['id'])
148
159
  end
149
160
  end
161
+
150
162
  it "in the case of an id conflict should not insert anything" do
151
163
  @r = @db.save({'lemons' => 'from texas', 'and' => 'how', "_id" => "oneB"})
152
164
 
@@ -159,12 +171,25 @@ describe CouchRest::Database do
159
171
  end.should raise_error(RestClient::RequestFailed)
160
172
 
161
173
  lambda do
162
- @db.get('twoB')
174
+ @db.get('twoB')
163
175
  end.should raise_error(RestClient::ResourceNotFound)
164
176
  end
177
+ it "should raise an error that is useful for recovery" do
178
+ @r = @db.save({"_id" => "taken", "field" => "stuff"})
179
+ begin
180
+ rs = @db.bulk_save([
181
+ {"_id" => "taken", "wild" => "and random"},
182
+ {"_id" => "free", "mild" => "yet local"},
183
+ {"another" => ["set","of","keys"]}
184
+ ])
185
+ rescue RestClient::RequestFailed => e
186
+ # soon CouchDB will provide _which_ docs conflicted
187
+ JSON.parse(e.response.body)['error'].should == 'conflict'
188
+ end
189
+ end
165
190
  end
166
191
 
167
- describe "POST (new document without an id)" do
192
+ describe "new document without an id" do
168
193
  it "should start empty" do
169
194
  @db.documents["total_rows"].should == 0
170
195
  end
@@ -173,6 +198,11 @@ describe CouchRest::Database do
173
198
  r2 = @db.get(r['id'])
174
199
  r2["lemons"].should == "from texas"
175
200
  end
201
+ it "should use PUT with UUIDs" do
202
+ CouchRest.should_receive(:put)
203
+ r = @db.save({'just' => ['another document']})
204
+ end
205
+
176
206
  end
177
207
 
178
208
  describe "PUT document with attachment" do
@@ -0,0 +1,23 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe CouchRest::Streamer do
4
+ before(:all) do
5
+ @cr = CouchRest.new(COUCHHOST)
6
+ @db = @cr.database(TESTDB)
7
+ @db.delete! rescue nil
8
+ @db = @cr.create_db(TESTDB) rescue nil
9
+ @streamer = CouchRest::Streamer.new(@db)
10
+ @docs = (1..1000).collect{|i| {:integer => i, :string => i.to_s}}
11
+ @db.bulk_save(@docs)
12
+ end
13
+
14
+ it "should yield each row in a view" do
15
+ count = 0
16
+ sum = 0
17
+ @streamer.view("_all_docs") do |row|
18
+ count += 1
19
+ end
20
+ count.should == 1000
21
+ end
22
+
23
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jchris-couchrest
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.1
4
+ version: 0.9.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - J. Chris Anderson
@@ -46,14 +46,16 @@ files:
46
46
  - lib/database.rb
47
47
  - lib/pager.rb
48
48
  - lib/file_manager.rb
49
+ - lib/streamer.rb
49
50
  - Rakefile
50
- - README
51
+ - README.markdown
51
52
  - bin/couchdir
52
53
  - bin/couchview
53
54
  - spec/couchrest_spec.rb
54
55
  - spec/database_spec.rb
55
56
  - spec/pager_spec.rb
56
57
  - spec/file_manager_spec.rb
58
+ - spec/streamer_spec.rb
57
59
  - spec/spec_helper.rb
58
60
  has_rdoc: false
59
61
  homepage: http://github.com/jchris/couchrest
data/README DELETED
@@ -1,39 +0,0 @@
1
- Couchrest is a loose port of the couch.js library from CouchDB’s Futon admin interface, which I find to be concise, clear, and well designed.
2
-
3
- I prefer to stay close to the metal, especially when the metal is as clean and simple as CouchDB’s HTTP API. The main thing Couchrest does for you is manage the Ruby <=> JSON serialization, and point your actions (database creation, document CRUD, view creation and queries, etc) at the appropriate API endpoints.
4
-
5
- The core of Couchrest is Heroku’s excellent REST Client Ruby HTTP wrapper. REST Client takes all the nastyness of Net::HTTP and gives is a pretty face, while still giving you more control than Open-URI. I recommend it anytime you’re interfacing with a well-defined API. (We use it for Grabb.it’s Tumblr integration, and it works like a charm.)
6
-
7
- The most complete source of documentation are the spec files. To ensure that your environment is setup for successful CouchRest operation, from the project root directory run `autotest` or `spec spec` (requires RSpec and optionally ZenTest for autotest support).
8
-
9
- Quick Start:
10
-
11
- @cr = CouchRest.new("http://localhost:5984")
12
- @db = @cr.create_db('couchrest-test')
13
- response = @db.save({:key => 'value', 'another key' => 'another value'})
14
- doc = @db.get(response['id'])
15
- puts doc.inspect
16
-
17
-
18
- Bulk Save:
19
-
20
- @db.bulk_save([
21
- {"wild" => "and random"},
22
- {"mild" => "yet local"},
23
- {"another" => ["set","of","keys"]}
24
- ])
25
- puts @db.documents.inspect # returns ids and revs of the current docs
26
-
27
- Creating and Querying Views:
28
-
29
- @db.save({
30
- "_id" => "_design/first",
31
- :views => {
32
- :test => {
33
- :map => "function(doc){for(var w in doc){ if(!w.match(/^_/))emit(w,doc[w])}}"
34
- }
35
- }
36
- })
37
- puts @db.view('first/test')['rows'].inspect
38
-
39
-