brianmario-couchrest 0.23
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/LICENSE +176 -0
- data/README.md +95 -0
- data/Rakefile +75 -0
- data/THANKS.md +18 -0
- data/examples/model/example.rb +144 -0
- data/examples/word_count/markov +38 -0
- data/examples/word_count/views/books/chunked-map.js +3 -0
- data/examples/word_count/views/books/united-map.js +1 -0
- data/examples/word_count/views/markov/chain-map.js +6 -0
- data/examples/word_count/views/markov/chain-reduce.js +7 -0
- data/examples/word_count/views/word_count/count-map.js +6 -0
- data/examples/word_count/views/word_count/count-reduce.js +3 -0
- data/examples/word_count/word_count.rb +46 -0
- data/examples/word_count/word_count_query.rb +40 -0
- data/examples/word_count/word_count_views.rb +26 -0
- data/lib/couchrest.rb +198 -0
- data/lib/couchrest/commands/generate.rb +71 -0
- data/lib/couchrest/commands/push.rb +103 -0
- data/lib/couchrest/core/database.rb +303 -0
- data/lib/couchrest/core/design.rb +79 -0
- data/lib/couchrest/core/document.rb +87 -0
- data/lib/couchrest/core/response.rb +16 -0
- data/lib/couchrest/core/server.rb +88 -0
- data/lib/couchrest/core/view.rb +4 -0
- data/lib/couchrest/helper/pager.rb +103 -0
- data/lib/couchrest/helper/streamer.rb +44 -0
- data/lib/couchrest/helper/upgrade.rb +51 -0
- data/lib/couchrest/mixins.rb +4 -0
- data/lib/couchrest/mixins/attachments.rb +31 -0
- data/lib/couchrest/mixins/callbacks.rb +483 -0
- data/lib/couchrest/mixins/class_proxy.rb +108 -0
- data/lib/couchrest/mixins/design_doc.rb +90 -0
- data/lib/couchrest/mixins/document_queries.rb +44 -0
- data/lib/couchrest/mixins/extended_attachments.rb +68 -0
- data/lib/couchrest/mixins/extended_document_mixins.rb +7 -0
- data/lib/couchrest/mixins/properties.rb +129 -0
- data/lib/couchrest/mixins/validation.rb +242 -0
- data/lib/couchrest/mixins/views.rb +169 -0
- data/lib/couchrest/monkeypatches.rb +113 -0
- data/lib/couchrest/more/casted_model.rb +28 -0
- data/lib/couchrest/more/extended_document.rb +215 -0
- data/lib/couchrest/more/property.rb +40 -0
- data/lib/couchrest/support/blank.rb +42 -0
- data/lib/couchrest/support/class.rb +176 -0
- data/lib/couchrest/validation/auto_validate.rb +163 -0
- data/lib/couchrest/validation/contextual_validators.rb +78 -0
- data/lib/couchrest/validation/validation_errors.rb +118 -0
- data/lib/couchrest/validation/validators/absent_field_validator.rb +74 -0
- data/lib/couchrest/validation/validators/confirmation_validator.rb +99 -0
- data/lib/couchrest/validation/validators/format_validator.rb +117 -0
- data/lib/couchrest/validation/validators/formats/email.rb +66 -0
- data/lib/couchrest/validation/validators/formats/url.rb +43 -0
- data/lib/couchrest/validation/validators/generic_validator.rb +120 -0
- data/lib/couchrest/validation/validators/length_validator.rb +134 -0
- data/lib/couchrest/validation/validators/method_validator.rb +89 -0
- data/lib/couchrest/validation/validators/numeric_validator.rb +104 -0
- data/lib/couchrest/validation/validators/required_field_validator.rb +109 -0
- data/spec/couchrest/core/couchrest_spec.rb +201 -0
- data/spec/couchrest/core/database_spec.rb +699 -0
- data/spec/couchrest/core/design_spec.rb +138 -0
- data/spec/couchrest/core/document_spec.rb +267 -0
- data/spec/couchrest/core/server_spec.rb +35 -0
- data/spec/couchrest/helpers/pager_spec.rb +122 -0
- data/spec/couchrest/helpers/streamer_spec.rb +23 -0
- data/spec/couchrest/more/casted_extended_doc_spec.rb +40 -0
- data/spec/couchrest/more/casted_model_spec.rb +98 -0
- data/spec/couchrest/more/extended_doc_attachment_spec.rb +130 -0
- data/spec/couchrest/more/extended_doc_spec.rb +509 -0
- data/spec/couchrest/more/extended_doc_subclass_spec.rb +98 -0
- data/spec/couchrest/more/extended_doc_view_spec.rb +355 -0
- data/spec/couchrest/more/property_spec.rb +136 -0
- data/spec/fixtures/attachments/README +3 -0
- data/spec/fixtures/attachments/couchdb.png +0 -0
- data/spec/fixtures/attachments/test.html +11 -0
- data/spec/fixtures/more/article.rb +34 -0
- data/spec/fixtures/more/card.rb +20 -0
- data/spec/fixtures/more/course.rb +14 -0
- data/spec/fixtures/more/event.rb +6 -0
- data/spec/fixtures/more/invoice.rb +17 -0
- data/spec/fixtures/more/person.rb +8 -0
- data/spec/fixtures/more/question.rb +6 -0
- data/spec/fixtures/more/service.rb +12 -0
- data/spec/fixtures/views/lib.js +3 -0
- data/spec/fixtures/views/test_view/lib.js +3 -0
- data/spec/fixtures/views/test_view/only-map.js +4 -0
- data/spec/fixtures/views/test_view/test-map.js +3 -0
- data/spec/fixtures/views/test_view/test-reduce.js +3 -0
- data/spec/spec.opts +6 -0
- data/spec/spec_helper.rb +26 -0
- data/utils/remap.rb +27 -0
- data/utils/subset.rb +30 -0
- metadata +200 -0
@@ -0,0 +1,103 @@
|
|
1
|
+
module CouchRest
|
2
|
+
|
3
|
+
module Commands
|
4
|
+
|
5
|
+
module Push
|
6
|
+
|
7
|
+
def self.run(options)
|
8
|
+
directory = options[:directory]
|
9
|
+
database = options[:trailing_args].first
|
10
|
+
|
11
|
+
fm = CouchRest::FileManager.new(database)
|
12
|
+
fm.loud = options[:loud]
|
13
|
+
|
14
|
+
if options[:loud]
|
15
|
+
puts "Pushing views from directory #{directory} to database #{fm.db}"
|
16
|
+
end
|
17
|
+
|
18
|
+
fm.push_views(directory)
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.help
|
22
|
+
helpstring = <<-GEN
|
23
|
+
|
24
|
+
== Pushing views with Couchview ==
|
25
|
+
|
26
|
+
Usage: couchview push directory dbname
|
27
|
+
|
28
|
+
Couchview expects a specific filesystem layout for your CouchDB views (see
|
29
|
+
example below). It also supports advanced features like inlining of library
|
30
|
+
code (so you can keep DRY) as well as avoiding unnecessary document
|
31
|
+
modification.
|
32
|
+
|
33
|
+
Couchview also solves a problem with CouchDB's view API, which only provides
|
34
|
+
access to the final reduce side of any views which have both a map and a
|
35
|
+
reduce function defined. The intermediate map results are often useful for
|
36
|
+
development and production. CouchDB is smart enough to reuse map indexes for
|
37
|
+
functions duplicated across views within the same design document.
|
38
|
+
|
39
|
+
For views with a reduce function defined, Couchview creates both a reduce view
|
40
|
+
and a map-only view, so that you can browse and query the map side as well as
|
41
|
+
the reduction, with no performance penalty.
|
42
|
+
|
43
|
+
== Example ==
|
44
|
+
|
45
|
+
couchview push foo-project/bar-views baz-database
|
46
|
+
|
47
|
+
This will push the views defined in foo-project/bar-views into a database
|
48
|
+
called baz-database. Couchview expects the views to be defined in files with
|
49
|
+
names like:
|
50
|
+
|
51
|
+
foo-project/bar-views/my-design/viewname-map.js
|
52
|
+
foo-project/bar-views/my-design/viewname-reduce.js
|
53
|
+
foo-project/bar-views/my-design/noreduce-map.js
|
54
|
+
|
55
|
+
Pushed to => http://127.0.0.1:5984/baz-database/_design/my-design
|
56
|
+
|
57
|
+
And the design document:
|
58
|
+
{
|
59
|
+
"views" : {
|
60
|
+
"viewname-map" : {
|
61
|
+
"map" : "### contents of view-name-map.js ###"
|
62
|
+
},
|
63
|
+
"viewname-reduce" : {
|
64
|
+
"map" : "### contents of view-name-map.js ###",
|
65
|
+
"reduce" : "### contents of view-name-reduce.js ###"
|
66
|
+
},
|
67
|
+
"noreduce-map" : {
|
68
|
+
"map" : "### contents of noreduce-map.js ###"
|
69
|
+
}
|
70
|
+
}
|
71
|
+
}
|
72
|
+
|
73
|
+
Couchview will create a design document for each subdirectory of the views
|
74
|
+
directory specified on the command line.
|
75
|
+
|
76
|
+
== Library Inlining ==
|
77
|
+
|
78
|
+
Couchview can optionally inline library code into your views so you only have
|
79
|
+
to maintain it in one place. It looks for any files named lib.* in your
|
80
|
+
design-doc directory (for doc specific libs) and in the parent views directory
|
81
|
+
(for project global libs). These libraries are only inserted into views which
|
82
|
+
include the text
|
83
|
+
|
84
|
+
// !include lib
|
85
|
+
|
86
|
+
or
|
87
|
+
|
88
|
+
# !include lib
|
89
|
+
|
90
|
+
Couchview is a result of scratching my own itch. I'd be happy to make it more
|
91
|
+
general, so please contact me at jchris@grabb.it if you'd like to see anything
|
92
|
+
added or changed.
|
93
|
+
|
94
|
+
GEN
|
95
|
+
helpstring.gsub(/^ /, '')
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
@@ -0,0 +1,303 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
require "base64"
|
3
|
+
|
4
|
+
module CouchRest
|
5
|
+
class Database
|
6
|
+
attr_reader :server, :host, :name, :root, :uri
|
7
|
+
attr_accessor :bulk_save_cache_limit
|
8
|
+
|
9
|
+
# Create a CouchRest::Database adapter for the supplied CouchRest::Server
|
10
|
+
# and database name.
|
11
|
+
#
|
12
|
+
# ==== Parameters
|
13
|
+
# server<CouchRest::Server>:: database host
|
14
|
+
# name<String>:: database name
|
15
|
+
#
|
16
|
+
def initialize(server, name)
|
17
|
+
@name = name
|
18
|
+
@server = server
|
19
|
+
@host = server.uri
|
20
|
+
@uri = @root = "#{host}/#{name.gsub('/','%2F')}"
|
21
|
+
@streamer = Streamer.new(self)
|
22
|
+
@bulk_save_cache = []
|
23
|
+
@bulk_save_cache_limit = 500 # must be smaller than the uuid count
|
24
|
+
end
|
25
|
+
|
26
|
+
# returns the database's uri
|
27
|
+
def to_s
|
28
|
+
@uri
|
29
|
+
end
|
30
|
+
|
31
|
+
# GET the database info from CouchDB
|
32
|
+
def info
|
33
|
+
CouchRest.get @uri
|
34
|
+
end
|
35
|
+
|
36
|
+
# Query the <tt>_all_docs</tt> view. Accepts all the same arguments as view.
|
37
|
+
def documents(params = {})
|
38
|
+
keys = params.delete(:keys)
|
39
|
+
url = CouchRest.paramify_url "#{@uri}/_all_docs", params
|
40
|
+
if keys
|
41
|
+
CouchRest.post(url, {:keys => keys})
|
42
|
+
else
|
43
|
+
CouchRest.get url
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# load a set of documents by passing an array of ids
|
48
|
+
def get_bulk(ids)
|
49
|
+
documents(:keys => ids, :include_docs => true)
|
50
|
+
end
|
51
|
+
|
52
|
+
# POST a temporary view function to CouchDB for querying. This is not
|
53
|
+
# recommended, as you don't get any performance benefit from CouchDB's
|
54
|
+
# materialized views. Can be quite slow on large databases.
|
55
|
+
def slow_view(funcs, params = {})
|
56
|
+
keys = params.delete(:keys)
|
57
|
+
funcs = funcs.merge({:keys => keys}) if keys
|
58
|
+
url = CouchRest.paramify_url "#{@uri}/_temp_view", params
|
59
|
+
Yajl::Parser.parse(RestClient.post(url, Yajl::Encoder.encode(funcs), {"Content-Type" => 'application/json'}))
|
60
|
+
end
|
61
|
+
|
62
|
+
# backwards compatibility is a plus
|
63
|
+
alias :temp_view :slow_view
|
64
|
+
|
65
|
+
# Query a CouchDB view as defined by a <tt>_design</tt> document. Accepts
|
66
|
+
# paramaters as described in http://wiki.apache.org/couchdb/HttpViewApi
|
67
|
+
def view(name, params = {}, &block)
|
68
|
+
keys = params.delete(:keys)
|
69
|
+
name = name.split('/') # I think this will always be length == 2, but maybe not...
|
70
|
+
dname = name.shift
|
71
|
+
vname = name.join('/')
|
72
|
+
url = CouchRest.paramify_url "#{@uri}/_design/#{dname}/_view/#{vname}", params
|
73
|
+
if keys
|
74
|
+
CouchRest.post(url, {:keys => keys})
|
75
|
+
else
|
76
|
+
if block_given?
|
77
|
+
@streamer.view("_design/#{dname}/_view/#{vname}", params, &block)
|
78
|
+
else
|
79
|
+
CouchRest.get url
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# GET a document from CouchDB, by id. Returns a Ruby Hash.
|
85
|
+
def get(id, params = {})
|
86
|
+
slug = escape_docid(id)
|
87
|
+
url = CouchRest.paramify_url("#{@uri}/#{slug}", params)
|
88
|
+
result = CouchRest.get(url)
|
89
|
+
return result unless result.is_a?(Hash)
|
90
|
+
doc = if /^_design/ =~ result["_id"]
|
91
|
+
Design.new(result)
|
92
|
+
else
|
93
|
+
Document.new(result)
|
94
|
+
end
|
95
|
+
doc.database = self
|
96
|
+
doc
|
97
|
+
end
|
98
|
+
|
99
|
+
# GET an attachment directly from CouchDB
|
100
|
+
def fetch_attachment(doc, name)
|
101
|
+
# slug = escape_docid(docid)
|
102
|
+
# name = CGI.escape(name)
|
103
|
+
uri = uri_for_attachment(doc, name)
|
104
|
+
RestClient.get uri
|
105
|
+
# "#{@uri}/#{slug}/#{name}"
|
106
|
+
end
|
107
|
+
|
108
|
+
# PUT an attachment directly to CouchDB
|
109
|
+
def put_attachment(doc, name, file, options = {})
|
110
|
+
docid = escape_docid(doc['_id'])
|
111
|
+
name = CGI.escape(name)
|
112
|
+
uri = uri_for_attachment(doc, name)
|
113
|
+
Yajl::Parser.parse(RestClient.put(uri, file, options))
|
114
|
+
end
|
115
|
+
|
116
|
+
# DELETE an attachment directly from CouchDB
|
117
|
+
def delete_attachment doc, name
|
118
|
+
uri = uri_for_attachment(doc, name)
|
119
|
+
# this needs a rev
|
120
|
+
Yajl::Parser.parse(RestClient.delete(uri))
|
121
|
+
end
|
122
|
+
|
123
|
+
# Save a document to CouchDB. This will use the <tt>_id</tt> field from
|
124
|
+
# the document as the id for PUT, or request a new UUID from CouchDB, if
|
125
|
+
# no <tt>_id</tt> is present on the document. IDs are attached to
|
126
|
+
# documents on the client side because POST has the curious property of
|
127
|
+
# being automatically retried by proxies in the event of network
|
128
|
+
# segmentation and lost responses.
|
129
|
+
#
|
130
|
+
# If <tt>bulk</tt> is true (false by default) the document is cached for bulk-saving later.
|
131
|
+
# Bulk saving happens automatically when #bulk_save_cache limit is exceded, or on the next non bulk save.
|
132
|
+
def save_doc(doc, bulk = false)
|
133
|
+
if doc['_attachments']
|
134
|
+
doc['_attachments'] = encode_attachments(doc['_attachments'])
|
135
|
+
end
|
136
|
+
if bulk
|
137
|
+
@bulk_save_cache << doc
|
138
|
+
return bulk_save if @bulk_save_cache.length >= @bulk_save_cache_limit
|
139
|
+
return {"ok" => true} # Compatibility with Document#save
|
140
|
+
elsif !bulk && @bulk_save_cache.length > 0
|
141
|
+
bulk_save
|
142
|
+
end
|
143
|
+
result = if doc['_id']
|
144
|
+
slug = escape_docid(doc['_id'])
|
145
|
+
CouchRest.put "#{@uri}/#{slug}", doc
|
146
|
+
else
|
147
|
+
begin
|
148
|
+
slug = doc['_id'] = @server.next_uuid
|
149
|
+
CouchRest.put "#{@uri}/#{slug}", doc
|
150
|
+
rescue #old version of couchdb
|
151
|
+
CouchRest.post @uri, doc
|
152
|
+
end
|
153
|
+
end
|
154
|
+
if result['ok']
|
155
|
+
doc['_id'] = result['id']
|
156
|
+
doc['_rev'] = result['rev']
|
157
|
+
doc.database = self if doc.respond_to?(:database=)
|
158
|
+
end
|
159
|
+
result
|
160
|
+
end
|
161
|
+
|
162
|
+
### DEPRECATION NOTICE
|
163
|
+
def save(doc, bulk=false)
|
164
|
+
puts "CouchRest::Database's save method is being deprecated, please use save_doc instead"
|
165
|
+
save_doc(doc, bulk)
|
166
|
+
end
|
167
|
+
|
168
|
+
|
169
|
+
# POST an array of documents to CouchDB. If any of the documents are
|
170
|
+
# missing ids, supply one from the uuid cache.
|
171
|
+
#
|
172
|
+
# If called with no arguments, bulk saves the cache of documents to be bulk saved.
|
173
|
+
def bulk_save(docs = nil, use_uuids = true)
|
174
|
+
if docs.nil?
|
175
|
+
docs = @bulk_save_cache
|
176
|
+
@bulk_save_cache = []
|
177
|
+
end
|
178
|
+
if (use_uuids)
|
179
|
+
ids, noids = docs.partition{|d|d['_id']}
|
180
|
+
uuid_count = [noids.length, @server.uuid_batch_count].max
|
181
|
+
noids.each do |doc|
|
182
|
+
nextid = @server.next_uuid(uuid_count) rescue nil
|
183
|
+
doc['_id'] = nextid if nextid
|
184
|
+
end
|
185
|
+
end
|
186
|
+
CouchRest.post "#{@uri}/_bulk_docs", {:docs => docs}
|
187
|
+
end
|
188
|
+
alias :bulk_delete :bulk_save
|
189
|
+
|
190
|
+
# DELETE the document from CouchDB that has the given <tt>_id</tt> and
|
191
|
+
# <tt>_rev</tt>.
|
192
|
+
#
|
193
|
+
# If <tt>bulk</tt> is true (false by default) the deletion is recorded for bulk-saving (bulk-deletion :) later.
|
194
|
+
# Bulk saving happens automatically when #bulk_save_cache limit is exceded, or on the next non bulk save.
|
195
|
+
def delete_doc(doc, bulk = false)
|
196
|
+
raise ArgumentError, "_id and _rev required for deleting" unless doc['_id'] && doc['_rev']
|
197
|
+
if bulk
|
198
|
+
@bulk_save_cache << { '_id' => doc['_id'], '_rev' => doc['_rev'], '_deleted' => true }
|
199
|
+
return bulk_save if @bulk_save_cache.length >= @bulk_save_cache_limit
|
200
|
+
return { "ok" => true } # Mimic the non-deferred version
|
201
|
+
end
|
202
|
+
slug = escape_docid(doc['_id'])
|
203
|
+
CouchRest.delete "#{@uri}/#{slug}?rev=#{doc['_rev']}"
|
204
|
+
end
|
205
|
+
|
206
|
+
### DEPRECATION NOTICE
|
207
|
+
def delete(doc, bulk=false)
|
208
|
+
puts "CouchRest::Database's delete method is being deprecated, please use delete_doc instead"
|
209
|
+
delete_doc(doc, bulk)
|
210
|
+
end
|
211
|
+
|
212
|
+
# COPY an existing document to a new id. If the destination id currently exists, a rev must be provided.
|
213
|
+
# <tt>dest</tt> can take one of two forms if overwriting: "id_to_overwrite?rev=revision" or the actual doc
|
214
|
+
# hash with a '_rev' key
|
215
|
+
def copy_doc(doc, dest)
|
216
|
+
raise ArgumentError, "_id is required for copying" unless doc['_id']
|
217
|
+
slug = escape_docid(doc['_id'])
|
218
|
+
destination = if dest.respond_to?(:has_key?) && dest['_id'] && dest['_rev']
|
219
|
+
"#{dest['_id']}?rev=#{dest['_rev']}"
|
220
|
+
else
|
221
|
+
dest
|
222
|
+
end
|
223
|
+
CouchRest.copy "#{@uri}/#{slug}", destination
|
224
|
+
end
|
225
|
+
|
226
|
+
### DEPRECATION NOTICE
|
227
|
+
def copy(doc, dest)
|
228
|
+
puts "CouchRest::Database's copy method is being deprecated, please use copy_doc instead"
|
229
|
+
copy_doc(doc, dest)
|
230
|
+
end
|
231
|
+
|
232
|
+
# Compact the database, removing old document revisions and optimizing space use.
|
233
|
+
def compact!
|
234
|
+
CouchRest.post "#{@uri}/_compact"
|
235
|
+
end
|
236
|
+
|
237
|
+
# Create the database
|
238
|
+
def create!
|
239
|
+
bool = server.create_db(@name) rescue false
|
240
|
+
bool && true
|
241
|
+
end
|
242
|
+
|
243
|
+
# Delete and re create the database
|
244
|
+
def recreate!
|
245
|
+
delete!
|
246
|
+
create!
|
247
|
+
rescue RestClient::ResourceNotFound
|
248
|
+
ensure
|
249
|
+
create!
|
250
|
+
end
|
251
|
+
|
252
|
+
# Replicates via "pulling" from another database to this database. Makes no attempt to deal with conflicts.
|
253
|
+
def replicate_from other_db
|
254
|
+
raise ArgumentError, "must provide a CouchReset::Database" unless other_db.kind_of?(CouchRest::Database)
|
255
|
+
CouchRest.post "#{@host}/_replicate", :source => other_db.root, :target => name
|
256
|
+
end
|
257
|
+
|
258
|
+
# Replicates via "pushing" to another database. Makes no attempt to deal with conflicts.
|
259
|
+
def replicate_to other_db
|
260
|
+
raise ArgumentError, "must provide a CouchReset::Database" unless other_db.kind_of?(CouchRest::Database)
|
261
|
+
CouchRest.post "#{@host}/_replicate", :target => other_db.root, :source => name
|
262
|
+
end
|
263
|
+
|
264
|
+
# DELETE the database itself. This is not undoable and could be rather
|
265
|
+
# catastrophic. Use with care!
|
266
|
+
def delete!
|
267
|
+
CouchRest.delete @uri
|
268
|
+
end
|
269
|
+
|
270
|
+
private
|
271
|
+
|
272
|
+
def uri_for_attachment(doc, name)
|
273
|
+
if doc.is_a?(String)
|
274
|
+
puts "CouchRest::Database#fetch_attachment will eventually require a doc as the first argument, not a doc.id"
|
275
|
+
docid = doc
|
276
|
+
rev = nil
|
277
|
+
else
|
278
|
+
docid = doc['_id']
|
279
|
+
rev = doc['_rev']
|
280
|
+
end
|
281
|
+
docid = escape_docid(docid)
|
282
|
+
name = CGI.escape(name)
|
283
|
+
rev = "?rev=#{doc['_rev']}" if rev
|
284
|
+
"#{@root}/#{docid}/#{name}#{rev}"
|
285
|
+
end
|
286
|
+
|
287
|
+
def escape_docid id
|
288
|
+
/^_design\/(.*)/ =~ id ? "_design/#{CGI.escape($1)}" : CGI.escape(id)
|
289
|
+
end
|
290
|
+
|
291
|
+
def encode_attachments(attachments)
|
292
|
+
attachments.each do |k,v|
|
293
|
+
next if v['stub']
|
294
|
+
v['data'] = base64(v['data'])
|
295
|
+
end
|
296
|
+
attachments
|
297
|
+
end
|
298
|
+
|
299
|
+
def base64(data)
|
300
|
+
Base64.encode64(data).gsub(/\s/,'')
|
301
|
+
end
|
302
|
+
end
|
303
|
+
end
|
@@ -0,0 +1,79 @@
|
|
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
|
+
# (using the database where this design doc was saved)
|
39
|
+
def view view_name, query={}, &block
|
40
|
+
view_on database, view_name, query, &block
|
41
|
+
end
|
42
|
+
|
43
|
+
# Dispatches to any named view in a specific database
|
44
|
+
def view_on db, view_name, query={}, &block
|
45
|
+
view_name = view_name.to_s
|
46
|
+
view_slug = "#{name}/#{view_name}"
|
47
|
+
defaults = (self['views'][view_name] && self['views'][view_name]["couchrest-defaults"]) || {}
|
48
|
+
db.view(view_slug, defaults.merge(query), &block)
|
49
|
+
end
|
50
|
+
|
51
|
+
def name
|
52
|
+
id.sub('_design/','') if id
|
53
|
+
end
|
54
|
+
|
55
|
+
def name= newname
|
56
|
+
self['_id'] = "_design/#{newname}"
|
57
|
+
end
|
58
|
+
|
59
|
+
def save
|
60
|
+
raise ArgumentError, "_design docs require a name" unless name && name.length > 0
|
61
|
+
super
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
# returns stored defaults if the there is a view named this in the design doc
|
67
|
+
def has_view?(view)
|
68
|
+
view = view.to_s
|
69
|
+
self['views'][view] &&
|
70
|
+
(self['views'][view]["couchrest-defaults"]||{})
|
71
|
+
end
|
72
|
+
|
73
|
+
def fetch_view view_name, opts, &block
|
74
|
+
database.view(view_name, opts, &block)
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|