paulcarey-relaxdb 0.2.1 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.textile +4 -2
- data/Rakefile +1 -1
- data/lib/relaxdb/document.rb +12 -13
- data/lib/relaxdb/has_one_proxy.rb +1 -1
- data/lib/relaxdb/paginate_params.rb +1 -1
- data/lib/relaxdb/paginator.rb +1 -1
- data/lib/relaxdb/query.rb +10 -1
- data/lib/relaxdb/relaxdb.rb +20 -5
- data/lib/relaxdb/server.rb +2 -1
- data/lib/relaxdb/sorted_by_view.rb +8 -7
- data/lib/relaxdb/view_result.rb +18 -0
- data/spec/document_spec.rb +17 -3
- data/spec/query_spec.rb +10 -0
- data/spec/relaxdb_spec.rb +28 -0
- metadata +2 -1
data/README.textile
CHANGED
@@ -2,6 +2,8 @@ h3. What's New?
|
|
2
2
|
|
3
3
|
* Pagination! CouchDB offers great support for retrieving a subset of data, but the housekeeping is tricky. RelaxDB takes care of it.
|
4
4
|
** Note that if you invoke paginate_by on an already created view, the necessary reduce function won't be automatically created. Take a look at SortedByView and create the reduce func by hand.
|
5
|
+
* Support for multi key post
|
6
|
+
** For example, @ Numbers.all.sorted_by(:val) { |q| q.keys([1,2,3,5]) } @
|
5
7
|
* Works with CouchDB 0.9 trunk as of 2008/10/08. Note that pagination won't work correctly on trunk until issue "COUCHDB-135":http://issues.apache.org/jira/browse/COUCHDB-135 is fixed.
|
6
8
|
|
7
9
|
*Note*: 0.2.1 requires CouchDB 0.9 trunk. 0.2.0 works with CouchDB 0.8 onwards.
|
@@ -78,10 +80,10 @@ h3. Paginating models
|
|
78
80
|
<code>
|
79
81
|
# Controller (merb-action-args used for extracting view_params)
|
80
82
|
|
81
|
-
def action(
|
83
|
+
def action(page_params={})
|
82
84
|
u_id = @user._id
|
83
85
|
|
84
|
-
@posts = Post.paginate_by(
|
86
|
+
@posts = Post.paginate_by(page_params, :writer_id, :created_at) do |p|
|
85
87
|
p.startkey([u_id, {}]).endkey([u_id]).descending(true).count(5)
|
86
88
|
end
|
87
89
|
render
|
data/Rakefile
CHANGED
data/lib/relaxdb/document.rb
CHANGED
@@ -136,9 +136,11 @@ module RelaxDB
|
|
136
136
|
end
|
137
137
|
end
|
138
138
|
|
139
|
+
# Order changed as of 30/10/2008 to be consistent with ActiveRecord
|
140
|
+
# Not yet sure of final implemention for hooks - may lean more towards DM than AR
|
139
141
|
def save
|
140
|
-
return false unless before_save
|
141
142
|
return false unless validates?
|
143
|
+
return false unless before_save
|
142
144
|
|
143
145
|
set_created_at if unsaved?
|
144
146
|
|
@@ -151,7 +153,7 @@ module RelaxDB
|
|
151
153
|
end
|
152
154
|
|
153
155
|
def validates?
|
154
|
-
|
156
|
+
total_success = true
|
155
157
|
properties.each do |prop|
|
156
158
|
if methods.include? "validate_#{prop}"
|
157
159
|
prop_val = instance_variable_get("@#{prop}")
|
@@ -161,9 +163,15 @@ module RelaxDB
|
|
161
163
|
@errors["#{prop}".to_sym] = send("#{prop}_validation_msg")
|
162
164
|
end
|
163
165
|
end
|
166
|
+
total_success &= success
|
164
167
|
end
|
165
168
|
end
|
166
|
-
|
169
|
+
total_success &= validate
|
170
|
+
total_success
|
171
|
+
end
|
172
|
+
|
173
|
+
def validate
|
174
|
+
true
|
167
175
|
end
|
168
176
|
|
169
177
|
# Hmm. Rename... never_saved? newnew?
|
@@ -171,6 +179,7 @@ module RelaxDB
|
|
171
179
|
@_rev.nil?
|
172
180
|
end
|
173
181
|
alias_method :new_record?, :unsaved?
|
182
|
+
alias_method :new_document?, :unsaved?
|
174
183
|
|
175
184
|
def to_param
|
176
185
|
self._id
|
@@ -352,16 +361,6 @@ module RelaxDB
|
|
352
361
|
end
|
353
362
|
end
|
354
363
|
|
355
|
-
#
|
356
|
-
# Document views are generated on demand if they don't exist when queried.
|
357
|
-
# The paginator is used with both document generated views, and user defined views.
|
358
|
-
# Auto generating document views on demand in the paginator makes it significantly
|
359
|
-
# more complex and less amendable to use by user defined views. For these reasons,
|
360
|
-
# the first call to paginate_by for a particular list of attributes for a particular
|
361
|
-
# Document class will update the corresponding design doc, even if it already contains
|
362
|
-
# the required view. This should not incur a penalty with CouchDB as it uses the
|
363
|
-
# same index for byte identical views.
|
364
|
-
#
|
365
364
|
def self.paginate_by(page_params, *view_keys)
|
366
365
|
paginate_params = PaginateParams.new
|
367
366
|
yield paginate_params
|
@@ -37,7 +37,7 @@ module RelaxDB
|
|
37
37
|
view_name = @relationship
|
38
38
|
view_path = "_view/#{design_doc}/#{view_name}?key=\"#{@client._id}\""
|
39
39
|
map_function = ViewCreator.has_n(@target_class, @relationship_as_viewed_by_target)
|
40
|
-
RelaxDB.retrieve(view_path, design_doc, view_name, map_function)
|
40
|
+
RelaxDB.retrieve(view_path, design_doc, view_name, map_function).first
|
41
41
|
end
|
42
42
|
|
43
43
|
end
|
@@ -2,7 +2,7 @@ module RelaxDB
|
|
2
2
|
|
3
3
|
class PaginateParams
|
4
4
|
|
5
|
-
@@params = %w(key startkey startkey_docid endkey endkey_docid count update descending group reduce)
|
5
|
+
@@params = %w(key startkey startkey_docid endkey endkey_docid count update descending group reduce include_docs)
|
6
6
|
|
7
7
|
@@params.each do |param|
|
8
8
|
define_method(param.to_sym) do |*val|
|
data/lib/relaxdb/paginator.rb
CHANGED
data/lib/relaxdb/query.rb
CHANGED
@@ -16,7 +16,8 @@ module RelaxDB
|
|
16
16
|
#
|
17
17
|
class Query
|
18
18
|
|
19
|
-
|
19
|
+
# keys is not included in the standard param as it is significantly different from the others
|
20
|
+
@@params = %w(key startkey startkey_docid endkey endkey_docid count update descending skip group group_level reduce include_docs)
|
20
21
|
|
21
22
|
@@params.each do |param|
|
22
23
|
define_method(param.to_sym) do |*val|
|
@@ -36,6 +37,14 @@ module RelaxDB
|
|
36
37
|
@view_name = view_name
|
37
38
|
end
|
38
39
|
|
40
|
+
def keys(keys=nil)
|
41
|
+
if keys.nil?
|
42
|
+
@keys
|
43
|
+
else
|
44
|
+
@keys = { :keys => keys }.to_json
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
39
48
|
def view_path
|
40
49
|
uri = "_view/#{@design_doc}/#{@view_name}"
|
41
50
|
|
data/lib/relaxdb/relaxdb.rb
CHANGED
@@ -47,10 +47,16 @@ module RelaxDB
|
|
47
47
|
data["ok"]
|
48
48
|
end
|
49
49
|
|
50
|
-
def load(
|
51
|
-
|
52
|
-
|
53
|
-
|
50
|
+
def load(*ids)
|
51
|
+
if ids.size == 1
|
52
|
+
resp = db.get(ids[0])
|
53
|
+
data = JSON.parse(resp.body)
|
54
|
+
create_object(data)
|
55
|
+
else
|
56
|
+
resp = db.post("_all_docs?include_docs=true", {:keys => ids}.to_json)
|
57
|
+
data = JSON.parse(resp.body)
|
58
|
+
data["rows"].map { |row| create_object(row["doc"]) }
|
59
|
+
end
|
54
60
|
end
|
55
61
|
|
56
62
|
# Used internally by RelaxDB
|
@@ -72,7 +78,7 @@ module RelaxDB
|
|
72
78
|
q = Query.new(design_doc, view_name)
|
73
79
|
yield q if block_given?
|
74
80
|
|
75
|
-
resp = db.get(q.view_path)
|
81
|
+
resp = q.keys ? db.post(q.view_path, q.keys) : db.get(q.view_path)
|
76
82
|
JSON.parse(resp.body)
|
77
83
|
end
|
78
84
|
|
@@ -136,10 +142,19 @@ module RelaxDB
|
|
136
142
|
|
137
143
|
# Convenience methods - should be in a diffent module?
|
138
144
|
|
145
|
+
def get(uri=nil)
|
146
|
+
JSON.parse(db.get(uri).body)
|
147
|
+
end
|
148
|
+
|
139
149
|
def pp_get(uri=nil)
|
140
150
|
resp = db.get(uri)
|
141
151
|
pp(JSON.parse(resp.body))
|
142
152
|
end
|
153
|
+
|
154
|
+
def pp_post(uri=nil, json=nil)
|
155
|
+
resp = db.post(uri, json)
|
156
|
+
pp(JSON.parse(resp.body))
|
157
|
+
end
|
143
158
|
|
144
159
|
end
|
145
160
|
|
data/lib/relaxdb/server.rb
CHANGED
@@ -84,7 +84,8 @@ module RelaxDB
|
|
84
84
|
@server.delete("/#{@db}/#{path}")
|
85
85
|
end
|
86
86
|
|
87
|
-
|
87
|
+
# *ignored allows methods to invoke get or post indifferently
|
88
|
+
def get(path=nil, *ignored)
|
88
89
|
@logger.info("GET /#{@db}/#{unesc(path)}")
|
89
90
|
@server.get("/#{@db}/#{path}")
|
90
91
|
end
|
@@ -31,25 +31,26 @@ module RelaxDB
|
|
31
31
|
end
|
32
32
|
|
33
33
|
def view_name
|
34
|
-
|
35
|
-
s << "_#{att}_and"
|
36
|
-
end
|
37
|
-
s[0, s.size-4]
|
34
|
+
"all_sorted_by_" << @atts.join("_and_")
|
38
35
|
end
|
39
36
|
|
40
37
|
def query(query)
|
41
38
|
# If a view contains both a map and reduce function, CouchDB will by default return
|
42
39
|
# the result of the reduce function when queried.
|
43
40
|
# This class automatically creates both map and reduce functions so it can be used by the paginator.
|
44
|
-
# In normal usage, this class will be used with map functions, hence reduce is explicitly set
|
41
|
+
# In normal usage, this class will be used with map functions, hence reduce is explicitly set
|
42
|
+
# to false if it hasn't already been set.
|
45
43
|
query.reduce(false) if query.reduce.nil?
|
46
44
|
|
45
|
+
# I hope the query.group(true) should be temporary only (given that reduce has been set to false)
|
46
|
+
method = query.keys ? (query.group(true) && :post) : :get
|
47
|
+
|
47
48
|
begin
|
48
|
-
resp = RelaxDB.db.
|
49
|
+
resp = RelaxDB.db.send(method, query.view_path, query.keys)
|
49
50
|
rescue => e
|
50
51
|
design_doc = DesignDocument.get(@class_name)
|
51
52
|
design_doc.add_map_view(view_name, map_function).add_reduce_view(view_name, reduce_function).save
|
52
|
-
resp = RelaxDB.db.
|
53
|
+
resp = RelaxDB.db.send(method, query.view_path, query.keys)
|
53
54
|
end
|
54
55
|
|
55
56
|
data = JSON.parse(resp.body)
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module RelaxDB
|
2
|
+
|
3
|
+
class ViewResult < DelegateClass(Array)
|
4
|
+
|
5
|
+
attr_reader :offset, :total_rows
|
6
|
+
|
7
|
+
def initialize(result_hash)
|
8
|
+
objs = RelaxDB.create_from_hash(result_hash)
|
9
|
+
|
10
|
+
@offset = result_hash["offset"]
|
11
|
+
@total_rows = result_hash["total_rows"]
|
12
|
+
|
13
|
+
super(objs)
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
data/spec/document_spec.rb
CHANGED
@@ -15,8 +15,7 @@ describe RelaxDB::Document do
|
|
15
15
|
describe ".new" do
|
16
16
|
|
17
17
|
it "should create an object with an id" do
|
18
|
-
|
19
|
-
p._id.should_not be_nil
|
18
|
+
Atom.new._id.should_not be_nil
|
20
19
|
end
|
21
20
|
|
22
21
|
it "should create an object with a nil revision" do
|
@@ -244,6 +243,12 @@ describe RelaxDB::Document do
|
|
244
243
|
User.new(:name => "atlas").save
|
245
244
|
User.all.sorted_by(:name, :age) { |q| q.startkey(["paul",0 ]).endkey(["paul", 50]) }.size.should == 1
|
246
245
|
end
|
246
|
+
|
247
|
+
it "should be retrievable by a multi key post" do
|
248
|
+
5.times { |i| Primitives.new(:num => i).save }
|
249
|
+
ps = Primitives.all.sorted_by(:num) { |q| q.keys([0, 4]) }
|
250
|
+
ps.map { |p| p.num }.should == [0, 4]
|
251
|
+
end
|
247
252
|
|
248
253
|
end
|
249
254
|
|
@@ -304,7 +309,7 @@ describe RelaxDB::Document do
|
|
304
309
|
r = Class.new(RelaxDB::Document) do
|
305
310
|
property :thumbs_up, :validator => lambda { |tu| tu >=0 && tu < 3 }
|
306
311
|
end
|
307
|
-
r.new(:thumbs_up => 2).save.
|
312
|
+
r.new(:thumbs_up => 2).save.should be
|
308
313
|
r.new(:thumbs_up => 3).save.should be_false
|
309
314
|
end
|
310
315
|
|
@@ -328,6 +333,15 @@ describe RelaxDB::Document do
|
|
328
333
|
x.errors[:bar].should == "rab"
|
329
334
|
end
|
330
335
|
|
336
|
+
it "should prevent saving unless all validations pass" do
|
337
|
+
r = Class.new(RelaxDB::Document) do
|
338
|
+
property :foo, :validator => lambda { false }
|
339
|
+
property :bar, :validator => lambda { true }
|
340
|
+
end
|
341
|
+
x = r.new
|
342
|
+
x.save.should == false
|
343
|
+
end
|
344
|
+
|
331
345
|
it "may be a proc" do
|
332
346
|
r = Class.new(RelaxDB::Document) do
|
333
347
|
property :thumbs_up, :validator => lambda { false }
|
data/spec/query_spec.rb
CHANGED
data/spec/relaxdb_spec.rb
CHANGED
@@ -58,6 +58,23 @@ describe RelaxDB do
|
|
58
58
|
|
59
59
|
end
|
60
60
|
|
61
|
+
describe ".load" do
|
62
|
+
|
63
|
+
it "should load a single document" do
|
64
|
+
a = Atom.new.save
|
65
|
+
ar = RelaxDB.load a._id
|
66
|
+
ar.should == a
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should load an arbitrary number of documents" do
|
70
|
+
a1, a2 = Atom.new.save, Atom.new.save
|
71
|
+
ar1, ar2 = RelaxDB.load a1._id, a2._id
|
72
|
+
ar1.should == a1
|
73
|
+
ar2.should == a2
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
61
78
|
describe ".view" do
|
62
79
|
|
63
80
|
map_func = %Q<
|
@@ -80,6 +97,17 @@ describe RelaxDB do
|
|
80
97
|
data["rows"].size.should == 1
|
81
98
|
end
|
82
99
|
|
100
|
+
it "should be queryable with a multi key post" do
|
101
|
+
5.times { |i| Primitives.new(:num => i).save }
|
102
|
+
# Create the view
|
103
|
+
Primitives.all.sorted_by(:num)
|
104
|
+
resp = RelaxDB.view("Primitives", "all_sorted_by_num") do |q|
|
105
|
+
q.keys([0, 4])
|
106
|
+
q.reduce(false).group(true) # group invocation should hopefully be temporary
|
107
|
+
end
|
108
|
+
RelaxDB.instantiate(resp).map{ |p| p.num }.should == [0, 4]
|
109
|
+
end
|
110
|
+
|
83
111
|
end
|
84
112
|
|
85
113
|
describe ".merge" do
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: paulcarey-relaxdb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Paul Carey
|
@@ -69,6 +69,7 @@ files:
|
|
69
69
|
- lib/relaxdb/sorted_by_view.rb
|
70
70
|
- lib/relaxdb/uuid_generator.rb
|
71
71
|
- lib/relaxdb/view_object.rb
|
72
|
+
- lib/relaxdb/view_result.rb
|
72
73
|
- lib/relaxdb/view_uploader.rb
|
73
74
|
- lib/relaxdb/views.rb
|
74
75
|
- lib/more/grapher.rb
|