relaxdb 0.3.5 → 0.5
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.textile +21 -23
- data/Rakefile +2 -7
- data/docs/spec_results.html +5 -5
- data/lib/more/grapher.rb +1 -1
- data/lib/relaxdb.rb +3 -5
- data/lib/relaxdb/all_delegator.rb +19 -13
- data/lib/relaxdb/document.rb +150 -218
- data/lib/relaxdb/extlib.rb +7 -1
- data/lib/relaxdb/migration.rb +11 -8
- data/lib/relaxdb/net_http_server.rb +19 -1
- data/lib/relaxdb/paginator.rb +30 -11
- data/lib/relaxdb/query.rb +1 -1
- data/lib/relaxdb/{belongs_to_proxy.rb → references_proxy.rb} +3 -3
- data/lib/relaxdb/relaxdb.rb +87 -7
- data/lib/relaxdb/server.rb +8 -2
- data/lib/relaxdb/taf2_curb_server.rb +2 -1
- data/lib/relaxdb/uuid_generator.rb +38 -2
- data/lib/relaxdb/view_by_delegator.rb +34 -0
- data/lib/relaxdb/view_object.rb +1 -1
- data/lib/relaxdb/view_uploader.rb +16 -2
- data/lib/relaxdb/views.rb +23 -55
- data/readme.rb +3 -3
- data/spec/all_delegator_spec.rb +52 -0
- data/spec/callbacks_spec.rb +4 -4
- data/spec/derived_properties_spec.rb +4 -4
- data/spec/design_doc_spec.rb +2 -2
- data/spec/doc_inheritable_spec.rb +2 -2
- data/spec/document_spec.rb +47 -25
- data/spec/migration_spec.rb +12 -10
- data/spec/qpaginate_spec.rb +88 -0
- data/spec/query_spec.rb +2 -2
- data/spec/references_proxy_spec.rb +94 -0
- data/spec/relaxdb_spec.rb +29 -21
- data/spec/server_spec.rb +4 -3
- data/spec/spec_helper.rb +1 -0
- data/spec/spec_models.rb +48 -57
- data/spec/uuid_generator_spec.rb +34 -0
- data/spec/view_by_spec.rb +62 -54
- data/spec/view_docs_by_spec.rb +85 -0
- metadata +38 -27
- data/lib/more/atomic_bulk_save_support.rb +0 -18
- data/lib/relaxdb/has_many_proxy.rb +0 -101
- data/lib/relaxdb/has_one_proxy.rb +0 -42
- data/lib/relaxdb/references_many_proxy.rb +0 -97
- data/spec/belongs_to_spec.rb +0 -124
- data/spec/has_many_spec.rb +0 -202
- data/spec/has_one_spec.rb +0 -123
- data/spec/references_many_spec.rb +0 -173
- data/spec/view_spec.rb +0 -23
data/lib/relaxdb/extlib.rb
CHANGED
@@ -16,9 +16,15 @@ class Time
|
|
16
16
|
# Ensure that all Times are stored as UTC
|
17
17
|
# Times in the following format may be passed directly to
|
18
18
|
# Date.new in a JavaScript runtime
|
19
|
-
def to_json
|
19
|
+
def to_json(options={})
|
20
20
|
utc
|
21
21
|
%Q("#{strftime "%Y/%m/%d %H:%M:%S +0000"}")
|
22
22
|
end
|
23
23
|
|
24
24
|
end
|
25
|
+
|
26
|
+
class Hash
|
27
|
+
def to_obj
|
28
|
+
RelaxDB.create_obj_from_doc(self)
|
29
|
+
end
|
30
|
+
end
|
data/lib/relaxdb/migration.rb
CHANGED
@@ -1,18 +1,21 @@
|
|
1
1
|
module RelaxDB
|
2
2
|
|
3
3
|
class Migration
|
4
|
+
|
5
|
+
def self.load_batch klass, startkey, limit
|
6
|
+
skip = startkey.nil? ? 0 : 1
|
7
|
+
result = RelaxDB.rf_view "#{klass}_all", :startkey => startkey,
|
8
|
+
:raw => true, :limit => limit, :skip => skip
|
9
|
+
ids = result["rows"].map { |h| h["id"] }
|
10
|
+
RelaxDB.load! ids
|
11
|
+
end
|
4
12
|
|
5
13
|
def self.run klass, limit = 1000
|
6
|
-
|
7
|
-
RelaxDB.paginate_view "#{klass}_all", :startkey => nil, :endkey => {}, :attributes => [:_id],
|
8
|
-
:page_params => page_params, :limit => limit
|
9
|
-
end
|
10
|
-
|
11
|
-
objs = query.call({})
|
14
|
+
objs = load_batch klass, nil, limit
|
12
15
|
until objs.empty?
|
13
|
-
migrated = objs.map { |o| yield o }.flatten.
|
16
|
+
migrated = objs.map { |o| yield o }.flatten.compact
|
14
17
|
RelaxDB.bulk_save! *migrated
|
15
|
-
objs =
|
18
|
+
objs = load_batch klass, objs[-1]._id, limit
|
16
19
|
end
|
17
20
|
end
|
18
21
|
|
@@ -5,6 +5,7 @@ module RelaxDB
|
|
5
5
|
def initialize(host, port)
|
6
6
|
@host = host
|
7
7
|
@port = port
|
8
|
+
@cx = Net::HTTP.start(@host, @port)
|
8
9
|
end
|
9
10
|
|
10
11
|
def delete(uri)
|
@@ -30,9 +31,26 @@ module RelaxDB
|
|
30
31
|
end
|
31
32
|
|
32
33
|
def request(req)
|
33
|
-
res = Net::HTTP.start(@host, @port) {|http|
|
34
|
+
res = Net::HTTP.start(@host, @port) { |http|
|
34
35
|
http.request(req)
|
35
36
|
}
|
37
|
+
|
38
|
+
#
|
39
|
+
# When using this chunk of code, as opposed to the one above, CouchDB
|
40
|
+
# started throwing emfile errors. I can't imagine they're related,
|
41
|
+
# but worth noting nonetheless. On second thoughts, I think they are!
|
42
|
+
#
|
43
|
+
# The tests don't close the cx after they run - just open new ones, this
|
44
|
+
# could be causing CouchDB to run out of handles.
|
45
|
+
#
|
46
|
+
# begin
|
47
|
+
# # @cx = Net::HTTP.start(@host, @port) unless @cx.active?
|
48
|
+
# res = @cx.request(req)
|
49
|
+
# rescue
|
50
|
+
# @cx = Net::HTTP.start(@host, @port)
|
51
|
+
# res = @cx.request(req)
|
52
|
+
# end
|
53
|
+
|
36
54
|
if (not res.kind_of?(Net::HTTPSuccess))
|
37
55
|
handle_error(req, res)
|
38
56
|
end
|
data/lib/relaxdb/paginator.rb
CHANGED
@@ -8,9 +8,23 @@ module RelaxDB
|
|
8
8
|
@paginate_params = paginate_params
|
9
9
|
@orig_paginate_params = @paginate_params.clone
|
10
10
|
|
11
|
-
page_params = page_params.is_a?(String) ? JSON.parse(page_params).to_mash : page_params
|
11
|
+
@page_params = page_params.is_a?(String) ? JSON.parse(page_params).to_mash : page_params
|
12
12
|
# Where the magic happens - the original params are updated with the page specific params
|
13
|
-
@paginate_params.update(page_params)
|
13
|
+
@paginate_params.update(@page_params)
|
14
|
+
end
|
15
|
+
|
16
|
+
def add_q_next_and_prev docs, view_name, view_keys
|
17
|
+
if docs.empty?
|
18
|
+
next_exists = prev_exists = false
|
19
|
+
else
|
20
|
+
next_exists = docs.size >= @paginate_params.limit
|
21
|
+
next_params = next_exists ? create_next(docs.last, view_keys) : false
|
22
|
+
|
23
|
+
prev_exists = !@page_params.empty?
|
24
|
+
prev_params = prev_exists ? create_prev(docs.first, view_keys) : false
|
25
|
+
end
|
26
|
+
|
27
|
+
add_next_and_prev_meths docs, next_params, prev_params
|
14
28
|
end
|
15
29
|
|
16
30
|
def total_doc_count(view_name)
|
@@ -31,22 +45,16 @@ module RelaxDB
|
|
31
45
|
total_doc_count = total_doc_count(view_name)
|
32
46
|
|
33
47
|
next_exists = !@paginate_params.order_inverted? ? (offset - orig_offset + no_docs < total_doc_count) : true
|
34
|
-
next_params = create_next(docs.last, view_keys)
|
48
|
+
next_params = next_exists ? create_next(docs.last, view_keys) : false
|
35
49
|
|
36
50
|
prev_exists = @paginate_params.order_inverted? ? (offset - orig_offset + no_docs < total_doc_count) :
|
37
51
|
(offset - orig_offset == 0 ? false : true)
|
38
|
-
prev_params = create_prev(docs.first, view_keys)
|
52
|
+
prev_params = prev_exists ? create_prev(docs.first, view_keys) : false
|
39
53
|
else
|
40
54
|
next_exists = prev_exists = false
|
41
55
|
end
|
42
56
|
|
43
|
-
docs
|
44
|
-
define_method(:next_params) { next_exists ? next_params : false }
|
45
|
-
define_method(:next_query) { next_exists ? "page_params=#{::CGI::escape(next_params.to_json)}" : false }
|
46
|
-
|
47
|
-
define_method(:prev_params) { prev_exists ? prev_params : false }
|
48
|
-
define_method(:prev_query) { prev_exists ? "page_params=#{::CGI::escape(prev_params.to_json)}" : false }
|
49
|
-
end
|
57
|
+
add_next_and_prev_meths docs, next_params, prev_params
|
50
58
|
end
|
51
59
|
|
52
60
|
def create_next(doc, view_keys)
|
@@ -83,6 +91,17 @@ module RelaxDB
|
|
83
91
|
RelaxDB.rf_view(view_name, params).offset
|
84
92
|
end
|
85
93
|
|
94
|
+
def add_next_and_prev_meths docs, next_params, prev_params
|
95
|
+
docs.meta_class.instance_eval do
|
96
|
+
define_method(:next_params) { next_params }
|
97
|
+
define_method(:next_query) { next_params ? "page_params=#{::CGI::escape(next_params.to_json)}" : false }
|
98
|
+
|
99
|
+
define_method(:prev_params) { prev_params }
|
100
|
+
define_method(:prev_query) { prev_params ? "page_params=#{::CGI::escape(prev_params.to_json)}" : false }
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
|
86
105
|
end
|
87
106
|
|
88
107
|
end
|
data/lib/relaxdb/query.rb
CHANGED
@@ -67,7 +67,7 @@ module RelaxDB
|
|
67
67
|
paginate_params.instance_variables.each do |pp|
|
68
68
|
val = paginate_params.instance_variable_get(pp)
|
69
69
|
method_name = pp[1, pp.length]
|
70
|
-
send(method_name, val) if
|
70
|
+
send(method_name, val) if @@params.include? method_name
|
71
71
|
end
|
72
72
|
end
|
73
73
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module RelaxDB
|
2
2
|
|
3
|
-
class
|
3
|
+
class ReferencesProxy
|
4
4
|
|
5
5
|
attr_reader :target
|
6
6
|
|
@@ -13,13 +13,13 @@ module RelaxDB
|
|
13
13
|
def target
|
14
14
|
return @target if @target
|
15
15
|
|
16
|
-
id = @client.
|
16
|
+
id = @client.data["#{@relationship}_id"]
|
17
17
|
@target = RelaxDB.load(id) if id
|
18
18
|
end
|
19
19
|
|
20
20
|
def target=(new_target)
|
21
21
|
id = new_target ? new_target._id : nil
|
22
|
-
@client.
|
22
|
+
@client.data["#{@relationship}_id"] = id
|
23
23
|
|
24
24
|
@target = new_target
|
25
25
|
end
|
data/lib/relaxdb/relaxdb.rb
CHANGED
@@ -23,10 +23,11 @@ module RelaxDB
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def enable_view_creation default=true
|
26
|
+
View.reset
|
26
27
|
@create_views = default
|
27
28
|
end
|
28
29
|
|
29
|
-
# Set in configuration and consulted by
|
30
|
+
# Set in configuration and consulted by view_docs_by, has_many, has_one, references_many and all
|
30
31
|
# Views will be added to CouchDB iff this is true
|
31
32
|
def create_views?
|
32
33
|
@create_views
|
@@ -121,16 +122,18 @@ module RelaxDB
|
|
121
122
|
# RelaxDB.logger.debug(caller.inject("#{db.name}/#{ids}\n") { |a, i| a += "#{i}\n" })
|
122
123
|
|
123
124
|
if ids.is_a? Array
|
125
|
+
return [] if ids.empty?
|
126
|
+
|
124
127
|
resp = db.post("_all_docs?include_docs=true", {:keys => ids}.to_json)
|
125
128
|
data = JSON.parse(resp.body)
|
126
|
-
data["rows"].map { |row| row["doc"] ?
|
129
|
+
data["rows"].map { |row| row["doc"] ? create_obj_from_doc(row["doc"]) : nil }
|
127
130
|
else
|
128
131
|
begin
|
129
132
|
qs = atts.map{ |k, v| "#{k}=#{v}" }.join("&")
|
130
133
|
qs = atts.empty? ? ids : "#{ids}?#{qs}"
|
131
134
|
resp = db.get qs
|
132
135
|
data = JSON.parse resp.body
|
133
|
-
|
136
|
+
create_obj_from_doc data
|
134
137
|
rescue HTTP_404
|
135
138
|
nil
|
136
139
|
end
|
@@ -146,6 +149,26 @@ module RelaxDB
|
|
146
149
|
res
|
147
150
|
end
|
148
151
|
|
152
|
+
#
|
153
|
+
# Queries a view that doesn't emit the underlying doc as a val.
|
154
|
+
#
|
155
|
+
def docs view_name, params
|
156
|
+
params[:raw] = true
|
157
|
+
params[:include_docs] = true
|
158
|
+
hash = view view_name, params
|
159
|
+
hash["rows"].map { |row| row["doc"] ? create_obj_from_doc(row["doc"]) : nil }
|
160
|
+
end
|
161
|
+
|
162
|
+
def doc_ids view_name, params
|
163
|
+
params[:raw] = true
|
164
|
+
result = view view_name, params
|
165
|
+
ids_from_view result
|
166
|
+
end
|
167
|
+
|
168
|
+
def ids_from_view raw_result
|
169
|
+
raw_result["rows"].map { |h| h["id"] }
|
170
|
+
end
|
171
|
+
|
149
172
|
#
|
150
173
|
# CouchDB defaults reduce to true when a reduce func is present.
|
151
174
|
# RelaxDB used to indiscriminately set reduce=false, allowing clients to override
|
@@ -195,7 +218,8 @@ module RelaxDB
|
|
195
218
|
|
196
219
|
def paginate_view(view_name, atts)
|
197
220
|
page_params = atts.delete :page_params
|
198
|
-
view_keys = atts.delete :attributes
|
221
|
+
view_keys = atts.delete :attributes
|
222
|
+
qpaginate = atts.delete :qpaginate
|
199
223
|
|
200
224
|
paginate_params = PaginateParams.new atts
|
201
225
|
raise paginate_params.error_msg if paginate_params.invalid?
|
@@ -209,17 +233,67 @@ module RelaxDB
|
|
209
233
|
docs = ViewResult.new(JSON.parse(db.get(query.view_path).body))
|
210
234
|
docs.reverse! if paginate_params.order_inverted?
|
211
235
|
|
212
|
-
|
236
|
+
if qpaginate
|
237
|
+
paginator.add_q_next_and_prev docs, view_name, view_keys
|
238
|
+
else
|
239
|
+
paginator.add_next_and_prev(docs, view_name, view_keys)
|
240
|
+
end
|
213
241
|
|
214
242
|
docs
|
215
243
|
end
|
216
|
-
|
244
|
+
|
245
|
+
#
|
246
|
+
# The paginate_view method only populates next and prev links if they exist.
|
247
|
+
# However, its implementation uses three queries to determine if the links
|
248
|
+
# should exist.
|
249
|
+
#
|
250
|
+
# It may be possible to determine if the links should exist with just
|
251
|
+
# a single query (by using a 1 doc offset on either side of the docs to be
|
252
|
+
# displayed).
|
253
|
+
#
|
254
|
+
# This method makes a small change to paginate_view, dispensing with knowledge
|
255
|
+
# of when to create prev and next links but only requiring a single query.
|
256
|
+
#
|
257
|
+
# Indiscrimate display of prev links makes this method more suited
|
258
|
+
# to forward navigation only.
|
259
|
+
#
|
260
|
+
def qpaginate_view view_name, atts
|
261
|
+
atts[:qpaginate] = true
|
262
|
+
paginate_view view_name, atts
|
263
|
+
end
|
264
|
+
|
265
|
+
#
|
266
|
+
# Paginates over views that don't emit the underlying doc as a val
|
267
|
+
# using the same idiom as qpaginate_view
|
268
|
+
#
|
269
|
+
def qpaginate_docs view_name, atts
|
270
|
+
page_params = atts.delete :page_params
|
271
|
+
view_keys = atts.delete :attributes
|
272
|
+
|
273
|
+
paginate_params = PaginateParams.new atts
|
274
|
+
raise paginate_params.error_msg if paginate_params.invalid?
|
275
|
+
paginator = Paginator.new(paginate_params, page_params)
|
276
|
+
|
277
|
+
atts[:raw] = true
|
278
|
+
query = Query.new(view_name, atts)
|
279
|
+
query.merge(paginate_params)
|
280
|
+
|
281
|
+
result = JSON.parse(db.get(query.view_path).body)
|
282
|
+
doc_ids = ids_from_view result
|
283
|
+
doc_ids.reverse! if paginate_params.order_inverted?
|
284
|
+
docs = RelaxDB.load! doc_ids
|
285
|
+
|
286
|
+
paginator.add_q_next_and_prev docs, view_name, view_keys
|
287
|
+
|
288
|
+
docs
|
289
|
+
end
|
290
|
+
|
217
291
|
def create_from_hash(data)
|
218
292
|
data["rows"].map { |row| create_object(row["value"]) }
|
219
293
|
end
|
220
294
|
|
221
295
|
def create_object(data)
|
222
|
-
klass = data.is_a?(Hash) && data
|
296
|
+
klass = data.is_a?(Hash) && data["relaxdb_class"]
|
223
297
|
if klass
|
224
298
|
k = klass.split("::").inject(Object) { |x, y| x.const_get y }
|
225
299
|
k.new data
|
@@ -228,6 +302,12 @@ module RelaxDB
|
|
228
302
|
ViewObject.create data
|
229
303
|
end
|
230
304
|
end
|
305
|
+
|
306
|
+
def create_obj_from_doc(data)
|
307
|
+
klass = data["relaxdb_class"]
|
308
|
+
k = klass.split("::").inject(Object) { |x, y| x.const_get y }
|
309
|
+
k.new data
|
310
|
+
end
|
231
311
|
|
232
312
|
# Convenience methods - should be in a diffent module?
|
233
313
|
|
data/lib/relaxdb/server.rb
CHANGED
@@ -6,7 +6,7 @@ module RelaxDB
|
|
6
6
|
|
7
7
|
class CouchDB
|
8
8
|
|
9
|
-
attr_reader :logger
|
9
|
+
attr_reader :logger, :server
|
10
10
|
|
11
11
|
# Used for test instrumentation only i.e. to assert that
|
12
12
|
# an expected number of requests have been issued
|
@@ -66,7 +66,13 @@ module RelaxDB
|
|
66
66
|
@put_count += 1
|
67
67
|
@logger.info("PUT /#{@db}/#{unesc(path)} #{json}")
|
68
68
|
@server.put("/#{@db}/#{path}", json)
|
69
|
-
|
69
|
+
end
|
70
|
+
|
71
|
+
def uuids(count=1)
|
72
|
+
@get_count += 1
|
73
|
+
uri = "/_uuids?count=#{count}"
|
74
|
+
@logger.info "GET #{uri}"
|
75
|
+
@server.get uri
|
70
76
|
end
|
71
77
|
|
72
78
|
def unesc(path)
|
@@ -8,6 +8,8 @@ module RelaxDB
|
|
8
8
|
end
|
9
9
|
end
|
10
10
|
|
11
|
+
attr_reader :host, :port
|
12
|
+
|
11
13
|
def initialize(host, port)
|
12
14
|
@host, @port = host, port
|
13
15
|
end
|
@@ -23,7 +25,6 @@ module RelaxDB
|
|
23
25
|
def put(uri, json)
|
24
26
|
request(uri, 'put') do |c|
|
25
27
|
c.headers['content-type'] = 'application/json'
|
26
|
-
c.headers['X-Couch-Full-Commit'] = "true"
|
27
28
|
c.http_put json
|
28
29
|
end
|
29
30
|
end
|
@@ -1,20 +1,56 @@
|
|
1
1
|
module RelaxDB
|
2
2
|
|
3
|
+
#
|
4
|
+
# UUIDs have a significant impact on CouchDB file size and performance
|
5
|
+
# See https://issues.apache.org/jira/browse/COUCHDB-465 and
|
6
|
+
# http://mail-archives.apache.org/mod_mbox/couchdb-dev/201001.mbox/%3chi57et$19n$1@ger.gmane.org%3e
|
7
|
+
#
|
8
|
+
# This approach uses the default CouchDB UUID generator (sequential) and
|
9
|
+
# reduces its size by converting to base 36. Converting to base 36 results
|
10
|
+
# in a UUID 7 chars shorter than hex.
|
11
|
+
#
|
12
|
+
# The default size of 200 is arbitrary. Brian Candler's UUID generator in
|
13
|
+
# couchtiny may also be of interest.
|
14
|
+
#
|
15
|
+
#
|
3
16
|
class UuidGenerator
|
17
|
+
|
18
|
+
@uuids = []
|
19
|
+
@count = 200
|
4
20
|
|
5
21
|
def self.uuid
|
6
22
|
unless @length
|
7
|
-
|
8
|
-
|
23
|
+
uuid = @uuids.pop
|
24
|
+
if uuid.nil?
|
25
|
+
refill
|
26
|
+
uuid = @uuids.pop
|
27
|
+
end
|
28
|
+
uuid.hex.to_s(36)
|
9
29
|
else
|
10
30
|
rand.to_s[2, @length]
|
11
31
|
end
|
12
32
|
end
|
33
|
+
|
34
|
+
def self.refill
|
35
|
+
resp = RelaxDB.db.uuids(@count)
|
36
|
+
@uuids = JSON.parse(resp.body)["uuids"]
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.count=(c)
|
40
|
+
@count = c
|
41
|
+
end
|
13
42
|
|
14
43
|
# Convenience that helps relationship debuggging and model exploration
|
15
44
|
def self.id_length=(length)
|
16
45
|
@length = length
|
17
46
|
end
|
47
|
+
|
48
|
+
# To be invoked by tests, or clients after temp changes
|
49
|
+
def self.reset
|
50
|
+
@uuids = []
|
51
|
+
@count = 200
|
52
|
+
@length = nil
|
53
|
+
end
|
18
54
|
|
19
55
|
end
|
20
56
|
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module RelaxDB
|
2
|
+
|
3
|
+
class ViewByDelegator < Delegator
|
4
|
+
|
5
|
+
def initialize(view_name, opts)
|
6
|
+
super([])
|
7
|
+
@view_name = view_name
|
8
|
+
@opts = opts
|
9
|
+
end
|
10
|
+
|
11
|
+
def __getobj__
|
12
|
+
unless @ids
|
13
|
+
@opts[:raw] = true
|
14
|
+
@ids = RelaxDB.doc_ids @view_name, @opts
|
15
|
+
end
|
16
|
+
@ids
|
17
|
+
end
|
18
|
+
|
19
|
+
def __setobj__ obj
|
20
|
+
# Intentionally empty
|
21
|
+
end
|
22
|
+
|
23
|
+
def load!
|
24
|
+
if @ids
|
25
|
+
RelaxDB.load! @ids
|
26
|
+
else
|
27
|
+
@opts[:include_docs] = true
|
28
|
+
RelaxDB.docs @view_name, @opts
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|