jchris-couchrest 0.9.12 → 0.12.2
Sign up to get free protection for your applications and to get access to all the features.
- data/{README.rdoc → README.md} +10 -8
- data/Rakefile +60 -39
- data/THANKS.md +18 -0
- data/examples/word_count/markov +1 -1
- data/examples/word_count/views/word_count/count-reduce.js +2 -2
- data/examples/word_count/word_count.rb +2 -2
- data/examples/word_count/word_count_query.rb +2 -2
- data/lib/couchrest/commands/push.rb +8 -4
- data/lib/couchrest/core/database.rb +95 -14
- data/lib/couchrest/core/design.rb +89 -0
- data/lib/couchrest/core/document.rb +63 -0
- data/lib/couchrest/core/model.rb +203 -120
- data/lib/couchrest/core/server.rb +1 -1
- data/lib/couchrest/core/view.rb +4 -0
- data/lib/couchrest/helper/pager.rb +10 -10
- data/lib/couchrest/monkeypatches.rb +1 -1
- data/lib/couchrest.rb +20 -2
- data/spec/couchrest/core/couchrest_spec.rb +33 -23
- data/spec/couchrest/core/database_spec.rb +163 -8
- data/spec/couchrest/core/design_spec.rb +131 -0
- data/spec/couchrest/core/document_spec.rb +130 -0
- data/spec/couchrest/core/model_spec.rb +319 -35
- data/spec/couchrest/helpers/pager_spec.rb +2 -2
- data/spec/fixtures/attachments/README +3 -0
- data/spec/spec_helper.rb +17 -3
- data/utils/remap.rb +2 -2
- data/utils/subset.rb +2 -2
- metadata +24 -33
- data/THANKS +0 -15
- data/bin/couchapp +0 -55
- data/bin/couchview +0 -48
- data/lib/couchrest/helper/file_manager.rb +0 -285
- data/lib/couchrest/helper/templates/example-map.js +0 -8
- data/lib/couchrest/helper/templates/example-reduce.js +0 -10
- data/lib/couchrest/helper/templates/index.html +0 -26
- data/spec/couchapp_spec.rb +0 -82
- data/spec/couchrest/helpers/file_manager_spec.rb +0 -170
data/{README.rdoc → README.md}
RENAMED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
# CouchRest: CouchDB, close to the metal
|
2
2
|
|
3
3
|
CouchRest is based on [CouchDB's couch.js test
|
4
4
|
library](http://svn.apache.org/repos/asf/incubator/couchdb/trunk/share/www/script/couch.js),
|
@@ -9,29 +9,29 @@ to CouchDB's API endpoints so you don't have to.
|
|
9
9
|
CouchRest's lighweight is designed to make a simple base for application and
|
10
10
|
framework-specific object oriented APIs.
|
11
11
|
|
12
|
-
|
12
|
+
## Easy Install
|
13
13
|
|
14
|
-
|
14
|
+
Easy Install is moving to RubyForge, heads up for the gem.
|
15
15
|
|
16
|
-
|
16
|
+
### Relax, it's RESTful
|
17
17
|
|
18
18
|
The core of Couchrest is Heroku’s excellent REST Client Ruby HTTP wrapper.
|
19
19
|
REST Client takes all the nastyness of Net::HTTP and gives is a pretty face,
|
20
20
|
while still giving you more control than Open-URI. I recommend it anytime
|
21
21
|
you’re interfacing with a well-defined web service.
|
22
22
|
|
23
|
-
|
23
|
+
### Running the Specs
|
24
24
|
|
25
25
|
The most complete documentation is the spec/ directory. To validate your
|
26
26
|
CouchRest install, from the project root directory run `rake`, or `autotest`
|
27
27
|
(requires RSpec and optionally ZenTest for autotest support).
|
28
28
|
|
29
|
-
|
29
|
+
## Examples
|
30
30
|
|
31
31
|
Quick Start:
|
32
32
|
|
33
33
|
# with !, it creates the database if it doesn't already exist
|
34
|
-
@db = CouchRest.database!("http://
|
34
|
+
@db = CouchRest.database!("http://127.0.0.1:5984/couchrest-test")
|
35
35
|
response = @db.save({:key => 'value', 'another key' => 'another value'})
|
36
36
|
doc = @db.get(response['id'])
|
37
37
|
puts doc.inspect
|
@@ -58,10 +58,12 @@ Creating and Querying Views:
|
|
58
58
|
})
|
59
59
|
puts @db.view('first/test')['rows'].inspect
|
60
60
|
|
61
|
-
|
61
|
+
## CouchRest::Model
|
62
62
|
|
63
63
|
CouchRest::Model is a module designed along the lines of DataMapper::Resource.
|
64
64
|
By subclassing, suddenly you get all sorts of powerful sugar, so that working
|
65
65
|
with CouchDB in your Rails or Merb app is no harder than working with the
|
66
66
|
standard SQL alternatives. See the CouchRest::Model documentation for an
|
67
67
|
example article class that illustrates usage.
|
68
|
+
|
69
|
+
CouchRest::Model will be removed from this package.
|
data/Rakefile
CHANGED
@@ -1,64 +1,85 @@
|
|
1
1
|
require 'rake'
|
2
2
|
require "rake/rdoctask"
|
3
|
-
require '
|
3
|
+
require 'rake/gempackagetask'
|
4
|
+
require File.join(File.expand_path(File.dirname(__FILE__)),'lib','couchrest')
|
4
5
|
|
5
6
|
|
7
|
+
begin
|
8
|
+
require 'spec/rake/spectask'
|
9
|
+
rescue LoadError
|
10
|
+
puts <<-EOS
|
11
|
+
To use rspec for testing you must install rspec gem:
|
12
|
+
gem install rspec
|
13
|
+
EOS
|
14
|
+
exit(0)
|
15
|
+
end
|
16
|
+
|
6
17
|
spec = Gem::Specification.new do |s|
|
7
18
|
s.name = "couchrest"
|
8
|
-
s.version =
|
9
|
-
s.date = "2008-
|
19
|
+
s.version = CouchRest::VERSION
|
20
|
+
s.date = "2008-11-22"
|
10
21
|
s.summary = "Lean and RESTful interface to CouchDB."
|
11
|
-
s.email = "jchris@
|
22
|
+
s.email = "jchris@apache.org"
|
12
23
|
s.homepage = "http://github.com/jchris/couchrest"
|
13
24
|
s.description = "CouchRest provides a simple interface on top of CouchDB's RESTful HTTP API, as well as including some utility scripts for managing views and attachments."
|
14
25
|
s.has_rdoc = true
|
15
26
|
s.authors = ["J. Chris Anderson"]
|
16
|
-
s.files = %w( LICENSE README.
|
17
|
-
|
27
|
+
s.files = %w( LICENSE README.md Rakefile THANKS.md ) +
|
28
|
+
Dir["{bin,examples,lib,spec,utils}/**/*"] -
|
29
|
+
Dir["spec/tmp"]
|
30
|
+
s.extra_rdoc_files = %w( README.md LICENSE THANKS.md )
|
18
31
|
s.require_path = "lib"
|
19
32
|
s.bindir = 'bin'
|
20
|
-
s.executables << 'couchview'
|
21
33
|
s.executables << 'couchdir'
|
22
|
-
s.executables << 'couchapp'
|
23
34
|
s.add_dependency("json", ">= 1.1.2")
|
24
35
|
s.add_dependency("rest-client", ">= 0.5")
|
36
|
+
s.add_dependency("mime-types", ">= 1.15")
|
25
37
|
s.add_dependency("extlib", ">= 0.9.6")
|
26
38
|
end
|
27
39
|
|
28
|
-
desc "Update Github Gemspec"
|
29
|
-
task :gemspec do
|
30
|
-
skip_fields = %w(new_platform original_platform)
|
31
|
-
integer_fields = %w(specification_version)
|
32
40
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
if name == "dependencies"
|
39
|
-
value.each do |d|
|
40
|
-
dep, *ver = d.to_s.split(" ")
|
41
|
-
result << " s.add_dependency #{dep.inspect}, [#{ /\(([^\,]*)/ . match(ver.join(" "))[1].inspect}]\n"
|
42
|
-
end
|
43
|
-
else
|
44
|
-
case value
|
45
|
-
when Array
|
46
|
-
value = name != "files" ? value.inspect : value.inspect.split(",").join(",\n")
|
47
|
-
when Fixnum
|
48
|
-
# leave as-is
|
49
|
-
when String
|
50
|
-
value = value.to_i if integer_fields.include?(name)
|
51
|
-
value = value.inspect
|
52
|
-
else
|
53
|
-
value = value.to_s.inspect
|
54
|
-
end
|
55
|
-
result << " s.#{name} = #{value}\n"
|
56
|
-
end
|
41
|
+
desc "create .gemspec file (useful for github)"
|
42
|
+
task :gemspec do
|
43
|
+
filename = "#{spec.name}.gemspec"
|
44
|
+
File.open(filename, "w") do |f|
|
45
|
+
f.puts spec.to_ruby
|
57
46
|
end
|
58
|
-
result << "end"
|
59
|
-
File.open(File.join(File.dirname(__FILE__), "#{spec.name}.gemspec"), "w"){|f| f << result}
|
60
47
|
end
|
61
48
|
|
49
|
+
# desc "Update Github Gemspec"
|
50
|
+
# task :gemspec do
|
51
|
+
# skip_fields = %w(new_platform original_platform)
|
52
|
+
# integer_fields = %w(specification_version)
|
53
|
+
#
|
54
|
+
# result = "Gem::Specification.new do |s|\n"
|
55
|
+
# spec.instance_variables.each do |ivar|
|
56
|
+
# value = spec.instance_variable_get(ivar)
|
57
|
+
# name = ivar.split("@").last
|
58
|
+
# next if skip_fields.include?(name) || value.nil? || value == "" || (value.respond_to?(:empty?) && value.empty?)
|
59
|
+
# if name == "dependencies"
|
60
|
+
# value.each do |d|
|
61
|
+
# dep, *ver = d.to_s.split(" ")
|
62
|
+
# result << " s.add_dependency #{dep.inspect}, [#{ /\(([^\,]*)/ . match(ver.join(" "))[1].inspect}]\n"
|
63
|
+
# end
|
64
|
+
# else
|
65
|
+
# case value
|
66
|
+
# when Array
|
67
|
+
# value = name != "files" ? value.inspect : value.inspect.split(",").join(",\n")
|
68
|
+
# when Fixnum
|
69
|
+
# # leave as-is
|
70
|
+
# when String
|
71
|
+
# value = value.to_i if integer_fields.include?(name)
|
72
|
+
# value = value.inspect
|
73
|
+
# else
|
74
|
+
# value = value.to_s.inspect
|
75
|
+
# end
|
76
|
+
# result << " s.#{name} = #{value}\n"
|
77
|
+
# end
|
78
|
+
# end
|
79
|
+
# result << "end"
|
80
|
+
# File.open(File.join(File.dirname(__FILE__), "#{spec.name}.gemspec"), "w"){|f| f << result}
|
81
|
+
# end
|
82
|
+
|
62
83
|
desc "Run all specs"
|
63
84
|
Spec::Rake::SpecTask.new('spec') do |t|
|
64
85
|
t.spec_files = FileList['spec/**/*_spec.rb']
|
@@ -78,5 +99,5 @@ Rake::RDocTask.new do |rdoc|
|
|
78
99
|
rdoc.title = "CouchRest: Ruby CouchDB, close to the metal"
|
79
100
|
end
|
80
101
|
|
81
|
-
desc "
|
102
|
+
desc "Run the rspec"
|
82
103
|
task :default => :spec
|
data/THANKS.md
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
CouchRest THANKS
|
2
|
+
=====================
|
3
|
+
|
4
|
+
CouchRest was originally developed by J. Chris Anderson <jchris@grabb.it>
|
5
|
+
and a number of other contributors. Many people further contributed to
|
6
|
+
CouchRest by reporting problems, suggesting various improvements or submitting
|
7
|
+
changes. A list of these people is included below.
|
8
|
+
|
9
|
+
* [Matt Aimonetti](http://merbist.com/about/)
|
10
|
+
* [Greg Borenstein](http://ideasfordozens.com)
|
11
|
+
* [Geoffrey Grosenbach](http://nubyonrails.com/)
|
12
|
+
* [Jonathan S. Katz](http://github.com/jkatz)
|
13
|
+
* [Matt Lyon](http://mattly.tumblr.com/)
|
14
|
+
* Simon Rozet (simon /at/ rozet /dot/ name)
|
15
|
+
|
16
|
+
Patches are welcome. The primary source for this software project is [on Github](http://github.com/jchris/couchrest/tree/master)
|
17
|
+
|
18
|
+
A lot of people have active forks - thank you all - even the patches I don't end up using are helpful.
|
data/examples/word_count/markov
CHANGED
@@ -1,3 +1,3 @@
|
|
1
|
-
function(key,
|
2
|
-
return sum(
|
1
|
+
function(key,values){
|
2
|
+
return sum(values);
|
3
3
|
}
|
@@ -1,6 +1,6 @@
|
|
1
1
|
require File.dirname(__FILE__) + '/../../couchrest'
|
2
2
|
|
3
|
-
couch = CouchRest.new("http://
|
3
|
+
couch = CouchRest.new("http://127.0.0.1:5984")
|
4
4
|
db = couch.database('word-count-example')
|
5
5
|
db.delete! rescue nil
|
6
6
|
db = couch.create_db('word-count-example')
|
@@ -62,6 +62,6 @@ end
|
|
62
62
|
# }
|
63
63
|
# })
|
64
64
|
|
65
|
-
# puts "The books have been stored in your CouchDB. To initiate the MapReduce process, visit http://
|
65
|
+
# puts "The books have been stored in your CouchDB. To initiate the MapReduce process, visit http://127.0.0.1:5984/_utils/ in your browser and click 'word-count-example', then select view 'words' or 'count'. The process could take about 15 minutes on an average MacBook."
|
66
66
|
#
|
67
67
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
require File.dirname(__FILE__) + '/../../couchrest'
|
2
2
|
|
3
|
-
couch = CouchRest.new("http://
|
3
|
+
couch = CouchRest.new("http://127.0.0.1:5984")
|
4
4
|
db = couch.database('word-count-example')
|
5
5
|
|
6
6
|
puts "Now that we've parsed all those books into CouchDB, the queries we can run are incredibly flexible."
|
@@ -35,5 +35,5 @@ puts "\nHere are the params for 'flight' in the da-vinci book:"
|
|
35
35
|
puts params.inspect
|
36
36
|
puts
|
37
37
|
puts 'The url looks like this:'
|
38
|
-
puts 'http://
|
38
|
+
puts 'http://127.0.0.1:5984/word-count-example/_view/word_count/count?key=["flight","da-vinci"]'
|
39
39
|
puts "\nTry dropping that in your browser..."
|
@@ -10,7 +10,11 @@ module CouchRest
|
|
10
10
|
|
11
11
|
fm = CouchRest::FileManager.new(database)
|
12
12
|
fm.loud = options[:loud]
|
13
|
-
|
13
|
+
|
14
|
+
if options[:loud]
|
15
|
+
puts "Pushing views from directory #{directory} to database #{fm.db}"
|
16
|
+
end
|
17
|
+
|
14
18
|
fm.push_views(directory)
|
15
19
|
end
|
16
20
|
|
@@ -48,7 +52,7 @@ module CouchRest
|
|
48
52
|
foo-project/bar-views/my-design/viewname-reduce.js
|
49
53
|
foo-project/bar-views/my-design/noreduce-map.js
|
50
54
|
|
51
|
-
Pushed to => http://
|
55
|
+
Pushed to => http://127.0.0.1:5984/baz-database/_design/my-design
|
52
56
|
|
53
57
|
And the design document:
|
54
58
|
{
|
@@ -77,11 +81,11 @@ module CouchRest
|
|
77
81
|
(for project global libs). These libraries are only inserted into views which
|
78
82
|
include the text
|
79
83
|
|
80
|
-
//include
|
84
|
+
// !include lib
|
81
85
|
|
82
86
|
or
|
83
87
|
|
84
|
-
#include
|
88
|
+
# !include lib
|
85
89
|
|
86
90
|
Couchview is a result of scratching my own itch. I'd be happy to make it more
|
87
91
|
general, so please contact me at jchris@grabb.it if you'd like to see anything
|
@@ -4,6 +4,7 @@ require "base64"
|
|
4
4
|
module CouchRest
|
5
5
|
class Database
|
6
6
|
attr_reader :server, :host, :name, :root
|
7
|
+
attr_accessor :bulk_save_cache_limit
|
7
8
|
|
8
9
|
# Create a CouchRest::Database adapter for the supplied CouchRest::Server
|
9
10
|
# and database name.
|
@@ -18,6 +19,8 @@ module CouchRest
|
|
18
19
|
@host = server.uri
|
19
20
|
@root = "#{host}/#{name}"
|
20
21
|
@streamer = Streamer.new(self)
|
22
|
+
@bulk_save_cache = []
|
23
|
+
@bulk_save_cache_limit = 50
|
21
24
|
end
|
22
25
|
|
23
26
|
# returns the database's uri
|
@@ -44,12 +47,15 @@ module CouchRest
|
|
44
47
|
# POST a temporary view function to CouchDB for querying. This is not
|
45
48
|
# recommended, as you don't get any performance benefit from CouchDB's
|
46
49
|
# materialized views. Can be quite slow on large databases.
|
47
|
-
def
|
50
|
+
def slow_view funcs, params = {}
|
48
51
|
keys = params.delete(:keys)
|
49
52
|
funcs = funcs.merge({:keys => keys}) if keys
|
50
|
-
url = CouchRest.paramify_url "#{@root}/
|
53
|
+
url = CouchRest.paramify_url "#{@root}/_slow_view", params
|
51
54
|
JSON.parse(RestClient.post(url, funcs.to_json, {"Content-Type" => 'application/json'}))
|
52
55
|
end
|
56
|
+
|
57
|
+
# backwards compatibility is a plus
|
58
|
+
alias :temp_view :slow_view
|
53
59
|
|
54
60
|
# Query a CouchDB view as defined by a <tt>_design</tt> document. Accepts
|
55
61
|
# paramaters as described in http://wiki.apache.org/couchdb/HttpViewApi
|
@@ -69,20 +75,27 @@ module CouchRest
|
|
69
75
|
|
70
76
|
# GET a document from CouchDB, by id. Returns a Ruby Hash.
|
71
77
|
def get id
|
72
|
-
slug =
|
73
|
-
CouchRest.get
|
78
|
+
slug = escape_docid(id)
|
79
|
+
hash = CouchRest.get("#{@root}/#{slug}")
|
80
|
+
doc = if /^_design/ =~ hash["_id"]
|
81
|
+
Design.new(hash)
|
82
|
+
else
|
83
|
+
Document.new(hash)
|
84
|
+
end
|
85
|
+
doc.database = self
|
86
|
+
doc
|
74
87
|
end
|
75
88
|
|
76
89
|
# GET an attachment directly from CouchDB
|
77
|
-
def fetch_attachment
|
78
|
-
|
90
|
+
def fetch_attachment docid, name
|
91
|
+
slug = escape_docid(docid)
|
79
92
|
name = CGI.escape(name)
|
80
|
-
RestClient.get "#{@root}/#{
|
93
|
+
RestClient.get "#{@root}/#{slug}/#{name}"
|
81
94
|
end
|
82
95
|
|
83
96
|
# PUT an attachment directly to CouchDB
|
84
97
|
def put_attachment doc, name, file, options = {}
|
85
|
-
docid =
|
98
|
+
docid = escape_docid(doc['_id'])
|
86
99
|
name = CGI.escape(name)
|
87
100
|
uri = if doc['_rev']
|
88
101
|
"#{@root}/#{docid}/#{name}?rev=#{doc['_rev']}"
|
@@ -99,12 +112,22 @@ module CouchRest
|
|
99
112
|
# documents on the client side because POST has the curious property of
|
100
113
|
# being automatically retried by proxies in the event of network
|
101
114
|
# segmentation and lost responses.
|
102
|
-
|
115
|
+
#
|
116
|
+
# If <tt>bulk</tt> is true (false by default) the document is cached for bulk-saving later.
|
117
|
+
# Bulk saving happens automatically when #bulk_save_cache limit is exceded, or on the next non bulk save.
|
118
|
+
def save (doc, bulk = false)
|
103
119
|
if doc['_attachments']
|
104
120
|
doc['_attachments'] = encode_attachments(doc['_attachments'])
|
105
121
|
end
|
106
|
-
if
|
107
|
-
|
122
|
+
if bulk
|
123
|
+
@bulk_save_cache << doc
|
124
|
+
return bulk_save if @bulk_save_cache.length >= @bulk_save_cache_limit
|
125
|
+
return {"ok" => true} # Compatibility with Document#save
|
126
|
+
elsif !bulk && @bulk_save_cache.length > 0
|
127
|
+
bulk_save
|
128
|
+
end
|
129
|
+
result = if doc['_id']
|
130
|
+
slug = escape_docid(doc['_id'])
|
108
131
|
CouchRest.put "#{@root}/#{slug}", doc
|
109
132
|
else
|
110
133
|
begin
|
@@ -114,11 +137,23 @@ module CouchRest
|
|
114
137
|
CouchRest.post @root, doc
|
115
138
|
end
|
116
139
|
end
|
140
|
+
if result['ok']
|
141
|
+
doc['_id'] = result['id']
|
142
|
+
doc['_rev'] = result['rev']
|
143
|
+
doc.database = self if doc.respond_to?(:database=)
|
144
|
+
end
|
145
|
+
result
|
117
146
|
end
|
118
147
|
|
119
148
|
# POST an array of documents to CouchDB. If any of the documents are
|
120
149
|
# missing ids, supply one from the uuid cache.
|
121
|
-
|
150
|
+
#
|
151
|
+
# If called with no arguments, bulk saves the cache of documents to be bulk saved.
|
152
|
+
def bulk_save (docs = nil)
|
153
|
+
if docs.nil?
|
154
|
+
docs = @bulk_save_cache
|
155
|
+
@bulk_save_cache = []
|
156
|
+
end
|
122
157
|
ids, noids = docs.partition{|d|d['_id']}
|
123
158
|
uuid_count = [noids.length, @server.uuid_batch_count].max
|
124
159
|
noids.each do |doc|
|
@@ -130,11 +165,53 @@ module CouchRest
|
|
130
165
|
|
131
166
|
# DELETE the document from CouchDB that has the given <tt>_id</tt> and
|
132
167
|
# <tt>_rev</tt>.
|
133
|
-
|
134
|
-
|
168
|
+
#
|
169
|
+
# If <tt>bulk</tt> is true (false by default) the deletion is recorded for bulk-saving (bulk-deletion :) later.
|
170
|
+
# Bulk saving happens automatically when #bulk_save_cache limit is exceded, or on the next non bulk save.
|
171
|
+
def delete (doc, bulk = false)
|
172
|
+
raise ArgumentError, "_id and _rev required for deleting" unless doc['_id'] && doc['_rev']
|
173
|
+
if bulk
|
174
|
+
@bulk_save_cache << { '_id' => doc['_id'], '_rev' => doc['_rev'], '_deleted' => true }
|
175
|
+
return bulk_save if @bulk_save_cache.length >= @bulk_save_cache_limit
|
176
|
+
return { "ok" => true } # Mimic the non-deferred version
|
177
|
+
end
|
178
|
+
slug = escape_docid(doc['_id'])
|
135
179
|
CouchRest.delete "#{@root}/#{slug}?rev=#{doc['_rev']}"
|
136
180
|
end
|
137
181
|
|
182
|
+
# COPY an existing document to a new id. If the destination id currently exists, a rev must be provided.
|
183
|
+
# <tt>dest</tt> can take one of two forms if overwriting: "id_to_overwrite?rev=revision" or the actual doc
|
184
|
+
# hash with a '_rev' key
|
185
|
+
def copy doc, dest
|
186
|
+
raise ArgumentError, "_id is required for copying" unless doc['_id']
|
187
|
+
slug = escape_docid(doc['_id'])
|
188
|
+
destination = if dest.respond_to?(:has_key?) && dest['_id'] && dest['_rev']
|
189
|
+
"#{dest['_id']}?rev=#{dest['_rev']}"
|
190
|
+
else
|
191
|
+
dest
|
192
|
+
end
|
193
|
+
CouchRest.copy "#{@root}/#{slug}", destination
|
194
|
+
end
|
195
|
+
|
196
|
+
# MOVE an existing document to a new id. If the destination id currently exists, a rev must be provided.
|
197
|
+
# <tt>dest</tt> can take one of two forms if overwriting: "id_to_overwrite?rev=revision" or the actual doc
|
198
|
+
# hash with a '_rev' key
|
199
|
+
def move doc, dest
|
200
|
+
raise ArgumentError, "_id and _rev are required for moving" unless doc['_id'] && doc['_rev']
|
201
|
+
slug = escape_docid(doc['_id'])
|
202
|
+
destination = if dest.respond_to?(:has_key?) && dest['_id'] && dest['_rev']
|
203
|
+
"#{dest['_id']}?rev=#{dest['_rev']}"
|
204
|
+
else
|
205
|
+
dest
|
206
|
+
end
|
207
|
+
CouchRest.move "#{@root}/#{slug}?rev=#{doc['_rev']}", destination
|
208
|
+
end
|
209
|
+
|
210
|
+
# Compact the database, removing old document revisions and optimizing space use.
|
211
|
+
def compact!
|
212
|
+
CouchRest.post "#{@root}/_compact"
|
213
|
+
end
|
214
|
+
|
138
215
|
# DELETE the database itself. This is not undoable and could be rather
|
139
216
|
# catastrophic. Use with care!
|
140
217
|
def delete!
|
@@ -143,6 +220,10 @@ module CouchRest
|
|
143
220
|
|
144
221
|
private
|
145
222
|
|
223
|
+
def escape_docid id
|
224
|
+
/^_design\/(.*)/ =~ id ? "_design/#{CGI.escape($1)}" : CGI.escape(id)
|
225
|
+
end
|
226
|
+
|
146
227
|
def encode_attachments attachments
|
147
228
|
attachments.each do |k,v|
|
148
229
|
next if v['stub']
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module CouchRest
|
2
|
+
class Design < Document
|
3
|
+
def view_by *keys
|
4
|
+
opts = keys.pop if keys.last.is_a?(Hash)
|
5
|
+
opts ||= {}
|
6
|
+
self['views'] ||= {}
|
7
|
+
method_name = "by_#{keys.join('_and_')}"
|
8
|
+
|
9
|
+
if opts[:map]
|
10
|
+
view = {}
|
11
|
+
view['map'] = opts.delete(:map)
|
12
|
+
if opts[:reduce]
|
13
|
+
view['reduce'] = opts.delete(:reduce)
|
14
|
+
opts[:reduce] = false
|
15
|
+
end
|
16
|
+
self['views'][method_name] = view
|
17
|
+
else
|
18
|
+
doc_keys = keys.collect{|k|"doc['#{k}']"} # this is where :require => 'doc.x == true' would show up
|
19
|
+
key_emit = doc_keys.length == 1 ? "#{doc_keys.first}" : "[#{doc_keys.join(', ')}]"
|
20
|
+
guards = opts.delete(:guards) || []
|
21
|
+
guards.concat doc_keys
|
22
|
+
map_function = <<-JAVASCRIPT
|
23
|
+
function(doc) {
|
24
|
+
if (#{guards.join(' && ')}) {
|
25
|
+
emit(#{key_emit}, null);
|
26
|
+
}
|
27
|
+
}
|
28
|
+
JAVASCRIPT
|
29
|
+
self['views'][method_name] = {
|
30
|
+
'map' => map_function
|
31
|
+
}
|
32
|
+
end
|
33
|
+
self['views'][method_name]['couchrest-defaults'] = opts unless opts.empty?
|
34
|
+
method_name
|
35
|
+
end
|
36
|
+
|
37
|
+
# Dispatches to any named view.
|
38
|
+
def view view_name, query={}, &block
|
39
|
+
view_name = view_name.to_s
|
40
|
+
view_slug = "#{name}/#{view_name}"
|
41
|
+
defaults = (self['views'][view_name] && self['views'][view_name]["couchrest-defaults"]) || {}
|
42
|
+
fetch_view(view_slug, defaults.merge(query), &block)
|
43
|
+
end
|
44
|
+
|
45
|
+
def name
|
46
|
+
id.sub('_design/','') if id
|
47
|
+
end
|
48
|
+
|
49
|
+
def name= newname
|
50
|
+
self['_id'] = "_design/#{newname}"
|
51
|
+
end
|
52
|
+
|
53
|
+
def save
|
54
|
+
raise ArgumentError, "_design docs require a name" unless name && name.length > 0
|
55
|
+
super
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
# returns stored defaults if the there is a view named this in the design doc
|
61
|
+
def has_view?(view)
|
62
|
+
view = view.to_s
|
63
|
+
self['views'][view] &&
|
64
|
+
(self['views'][view]["couchrest-defaults"]||{})
|
65
|
+
end
|
66
|
+
|
67
|
+
# def fetch_view_with_docs name, opts, raw=false, &block
|
68
|
+
# if raw
|
69
|
+
# fetch_view name, opts, &block
|
70
|
+
# else
|
71
|
+
# begin
|
72
|
+
# view = fetch_view name, opts.merge({:include_docs => true}), &block
|
73
|
+
# view['rows'].collect{|r|new(r['doc'])} if view['rows']
|
74
|
+
# rescue
|
75
|
+
# # fallback for old versions of couchdb that don't
|
76
|
+
# # have include_docs support
|
77
|
+
# view = fetch_view name, opts, &block
|
78
|
+
# view['rows'].collect{|r|new(database.get(r['id']))} if view['rows']
|
79
|
+
# end
|
80
|
+
# end
|
81
|
+
# end
|
82
|
+
|
83
|
+
def fetch_view view_name, opts, &block
|
84
|
+
database.view(view_name, opts, &block)
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module CouchRest
|
2
|
+
class Response < Hash
|
3
|
+
def initialize keys = {}
|
4
|
+
keys.each do |k,v|
|
5
|
+
self[k.to_s] = v
|
6
|
+
end
|
7
|
+
end
|
8
|
+
def []= key, value
|
9
|
+
super(key.to_s, value)
|
10
|
+
end
|
11
|
+
def [] key
|
12
|
+
super(key.to_s)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Document < Response
|
17
|
+
|
18
|
+
attr_accessor :database
|
19
|
+
|
20
|
+
# alias for self['_id']
|
21
|
+
def id
|
22
|
+
self['_id']
|
23
|
+
end
|
24
|
+
|
25
|
+
# alias for self['_rev']
|
26
|
+
def rev
|
27
|
+
self['_rev']
|
28
|
+
end
|
29
|
+
|
30
|
+
# returns true if the document has never been saved
|
31
|
+
def new_document?
|
32
|
+
!rev
|
33
|
+
end
|
34
|
+
|
35
|
+
# Saves the document to the db using create or update. Also runs the :save
|
36
|
+
# callbacks. Sets the <tt>_id</tt> and <tt>_rev</tt> fields based on
|
37
|
+
# CouchDB's response.
|
38
|
+
# If <tt>bulk</tt> is <tt>true</tt> (defaults to false) the document is cached for bulk save.
|
39
|
+
def save(bulk = false)
|
40
|
+
raise ArgumentError, "doc.database required for saving" unless database
|
41
|
+
result = database.save self, bulk
|
42
|
+
result['ok']
|
43
|
+
end
|
44
|
+
|
45
|
+
# Deletes the document from the database. Runs the :delete callbacks.
|
46
|
+
# Removes the <tt>_id</tt> and <tt>_rev</tt> fields, preparing the
|
47
|
+
# document to be saved to a new <tt>_id</tt>.
|
48
|
+
# If <tt>bulk</tt> is <tt>true</tt> (defaults to false) the document won't
|
49
|
+
# actually be deleted from the db until bulk save.
|
50
|
+
def destroy(bulk = false)
|
51
|
+
raise ArgumentError, "doc.database required to destroy" unless database
|
52
|
+
result = database.delete(self, bulk)
|
53
|
+
if result['ok']
|
54
|
+
self['_rev'] = nil
|
55
|
+
self['_id'] = nil
|
56
|
+
end
|
57
|
+
result['ok']
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
end
|