jchris-couchrest 0.9.12 → 0.12.2
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.
- 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
|